diff --git a/packages/core/src/dev/components/error-page/DarkModeIcon.astro b/packages/core/src/dev/components/error-page/DarkModeIcon.astro deleted file mode 100644 index 4edad01..0000000 --- a/packages/core/src/dev/components/error-page/DarkModeIcon.astro +++ /dev/null @@ -1,32 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - \ No newline at end of file diff --git a/packages/core/src/dev/components/error-page/ErrorDisplay.astro b/packages/core/src/dev/components/error-page/ErrorDisplay.astro deleted file mode 100644 index 915ee72..0000000 --- a/packages/core/src/dev/components/error-page/ErrorDisplay.astro +++ /dev/null @@ -1,193 +0,0 @@ ---- -import { Code } from "astro:components"; -import { readFile } from "node:fs/promises"; -import { transformerMetaHighlight } from "@shikijs/transformers"; -import { escapeHTML } from "astro/runtime/server/escape.js"; -import { createCssVariablesTheme } from "shiki"; -import type { ErrorWithMetadata } from "./types"; - -interface Props { - error: ErrorWithMetadata; -} - -const { error } = Astro.props; - -const fullCode = error.loc?.file - ? await readFile(error.loc.file, "utf-8") - : undefined; - -const separator = error.loc?.file?.includes("/") ? "/" : "\\"; -const fileLocation = [ - error.loc?.file?.split(separator).slice(-2).join("/"), - error.loc?.line, - error.loc?.column, -] - .filter(Boolean) - .join(":"); - -const theme = createCssVariablesTheme({ - variablePrefix: "--astro-code-", -}); - -const errorHintText = escapeHTML(error.hint ?? "") - .split(" ") - .map((v) => { - if (!v.startsWith("https://")) return v; - if (v.endsWith(".")) - return `${v.slice(0, -1)}.`; - return `${v}`; - }) - .join(" "); ---- - -
-

{error.name}

-

- {error.message} -

- { - errorHintText && ( -
- 🚀 -

-
- ) - } - {fileLocation &&

{fileLocation}

} - { - fullCode && ( - - ) - } -
- - - - - - diff --git a/packages/core/src/dev/components/error-page/ErrorPage.astro b/packages/core/src/dev/components/error-page/ErrorPage.astro deleted file mode 100644 index 55cd305..0000000 --- a/packages/core/src/dev/components/error-page/ErrorPage.astro +++ /dev/null @@ -1,94 +0,0 @@ ---- -import ErrorDisplay from "./ErrorDisplay.astro"; -import Header from "./Header.astro"; -import type { ErrorWithMetadata } from "./types"; - -interface Props { - error: ErrorWithMetadata; -} - -const { error } = Astro.props; ---- - - - - - Error - - - -
-
- -
- - - - diff --git a/packages/core/src/dev/components/error-page/Header.astro b/packages/core/src/dev/components/error-page/Header.astro deleted file mode 100644 index 4def227..0000000 --- a/packages/core/src/dev/components/error-page/Header.astro +++ /dev/null @@ -1,87 +0,0 @@ ---- -import DarkModeIcon from "./DarkModeIcon.astro"; -import LightModeIcon from "./LightModeIcon.astro"; -import MightyLogo from "./MightyLogo.astro"; ---- - -
-
-
- -
- - - - diff --git a/packages/core/src/dev/components/error-page/LightModeIcon.astro b/packages/core/src/dev/components/error-page/LightModeIcon.astro deleted file mode 100644 index faec933..0000000 --- a/packages/core/src/dev/components/error-page/LightModeIcon.astro +++ /dev/null @@ -1,36 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - - - - \ No newline at end of file diff --git a/packages/core/src/dev/components/error-page/MightyLogo.astro b/packages/core/src/dev/components/error-page/MightyLogo.astro deleted file mode 100644 index 102ea80..0000000 --- a/packages/core/src/dev/components/error-page/MightyLogo.astro +++ /dev/null @@ -1,71 +0,0 @@ ---- -interface Props { - class?: string; -} - -const { class: className } = Astro.props; ---- - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/core/src/dev/components/error-page/types.ts b/packages/core/src/dev/components/error-page/types.ts deleted file mode 100644 index 39617c9..0000000 --- a/packages/core/src/dev/components/error-page/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -type ErrorTypes = - | "AstroError" - | "AstroUserError" - | "CompilerError" - | "CSSError" - | "MarkdownError" - | "InternalError" - | "AggregateError"; - -export interface ErrorWithMetadata { - name: string; - type?: ErrorTypes; - message: string; - stack: string; - hint?: string; - loc?: { - file?: string; - line?: number; - column?: number; - }; -} diff --git a/packages/core/src/dev/index.ts b/packages/core/src/dev/index.ts index 81f657d..9699ff8 100644 --- a/packages/core/src/dev/index.ts +++ b/packages/core/src/dev/index.ts @@ -1,11 +1,11 @@ -import type { MightyDevMiddleware, MightyDevOptions } from "@/types"; +import type { MightyDevMiddleware, MightyServerOptions } from "@/types"; import { setupDev } from "./setup"; /** * Starts the Mighty development server. */ export async function dev( - options: MightyDevOptions, + options: MightyServerOptions, ): Promise { return setupDev(options); } diff --git a/packages/core/src/dev/setup.ts b/packages/core/src/dev/setup.ts index 5bb1e05..b0102b5 100644 --- a/packages/core/src/dev/setup.ts +++ b/packages/core/src/dev/setup.ts @@ -12,8 +12,8 @@ import type { ViteDevServer } from "vite"; import { getStylesForURL } from "@/dev/css"; import type { MightyDevMiddleware, - MightyDevOptions, MightyRenderDevRequest, + MightyServerOptions, } from "@/types"; import { dotStringToPath } from "@/utils/dotStringToPath"; import { injectTagsIntoHead } from "@/utils/injectTagsIntoHead"; @@ -31,7 +31,7 @@ const require = createRequire(import.meta.url); const devDir = path.join(path.dirname(require.resolve("@gomighty/core/dev"))); export async function setupDev( - options: MightyDevOptions, + options: MightyServerOptions, ): Promise { let finalConfig: AstroConfig; let viteServer: ViteDevServer; @@ -42,9 +42,6 @@ export async function setupDev( server: { middlewareMode: true, cors: false, - hmr: { - overlay: false, - }, }, plugins: [ { @@ -141,67 +138,12 @@ export async function setupDev( ] : () => []; - const renderComponentByPath = async ( - data: Omit & { - componentPath: `${string}.astro`; - }, - ) => { - const { componentPath, props, context, partial = true, address } = data; - - const [rawRenderedComponent, styleTags] = await Promise.all([ - renderComponent({ - componentPath, - props, - context, - partial, - }), - getStylesForURL(componentPath, viteServer).then((styles): Element[] => - styles.styles.map((style) => ({ - type: "element", - tagName: "style", - properties: { - type: "text/css", - "data-vite-dev-id": style.id, - }, - children: [{ type: "text", value: style.content }], - })), - ), - ]); - - // Rewrite image URLs to include the dev address - const renderedComponent = rawRenderedComponent.replace( - /(["'(])\/@fs\//g, - `$1${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@fs/`, - ); - - const viteClientScript: Element = { - type: "element", - tagName: "script", - properties: { - type: "module", - src: `${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@vite/client`, - }, - children: [], - }; - - return injectTagsIntoHead( - renderedComponent, - [ - ...styleTags, - viteClientScript, - ...getPageScripts(), - ...headInlineScriptTags, - ], - partial, - ).replaceAll(MIGHTY_DEV_PLACEHOLDER_ADDRESS, address); - }; - return { viteMiddleware: viteServer.middlewares, stop: () => viteServer.close(), render: async (request: MightyRenderDevRequest) => { try { - const { component, props, context, partial, address } = request; + const { component, props, context, partial = true, address } = request; const componentPath: `${string}.astro` = `${path.join( finalConfig.srcDir.pathname, @@ -216,39 +158,75 @@ export async function setupDev( return { status: 404, content: `Component ${component} not found` }; } - return { - status: 200, - content: await renderComponentByPath({ + const [rawRenderedComponent, styleTags] = await Promise.all([ + renderComponent({ componentPath, props, context, partial, - address, }), + getStylesForURL(componentPath, viteServer).then((styles): Element[] => + styles.styles.map((style) => ({ + type: "element", + tagName: "style", + properties: { + type: "text/css", + "data-vite-dev-id": style.id, + }, + children: [{ type: "text", value: style.content }], + })), + ), + ]); + + // Rewrite image URLs to include the dev address + const renderedComponent = rawRenderedComponent.replace( + /(["'(])\/@fs\//g, + `$1${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@fs/`, + ); + + const viteClientScript: Element = { + type: "element", + tagName: "script", + properties: { + type: "module", + src: `${MIGHTY_DEV_PLACEHOLDER_ADDRESS}/@vite/client`, + }, + children: [], }; + + const content = injectTagsIntoHead( + renderedComponent, + [ + ...styleTags, + viteClientScript, + ...getPageScripts(), + ...headInlineScriptTags, + ], + partial, + ).replaceAll(MIGHTY_DEV_PLACEHOLDER_ADDRESS, address); + + return { status: 200, content }; } catch (error) { viteServer.ssrFixStacktrace(error as Error); - if (!(options.showErrorPage ?? true)) { + const hmr = finalConfig.vite?.server?.hmr; + const overlayEnabled = + typeof hmr === "object" ? hmr.overlay !== false : hmr !== false; + + if (!overlayEnabled) { throw error; } + setTimeout(() => { + viteServer.environments.client.hot.send({ + type: "error", + err: error as Error & { stack: string }, + }); + }, 200); + return { status: 500, - content: await renderComponentByPath({ - componentPath: path.join( - devDir, - "components", - "error-page", - "ErrorPage.astro", - ) as `${string}.astro`, - props: { - error: error as Error, - }, - context: {}, - partial: false, - address: request.address, - }), + content: `${(error as Error).name}`, }; } }, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index afa69a2..ba9170f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -12,14 +12,6 @@ export type MightyServerOptions = { config?: AstroInlineConfig; }; -export type MightyDevOptions = MightyServerOptions & { - /** - * Whether to show the Mighty error page when an error occurs. - * @default true - */ - showErrorPage?: boolean; -}; - export type MightyStartOptions = MightyServerOptions; export type MightyRenderRequest = { diff --git a/packages/core/tests/dev/error.test.ts b/packages/core/tests/dev/error.test.ts index 008ed51..c163f1e 100644 --- a/packages/core/tests/dev/error.test.ts +++ b/packages/core/tests/dev/error.test.ts @@ -14,7 +14,7 @@ describe("dev error fixture", () => { await fixture.clean(); }); - it("can render an error page", async () => { + it("renders the Astro error overlay", async () => { const { render } = await fixture.startDevServer({ config: { logLevel: "silent" }, }); @@ -26,37 +26,17 @@ describe("dev error fixture", () => { partial: false, }); expect(response.status).toBe(500); - expect(response.content).toContain("Error"); expect(response.content).toContain( '', ); - expect(response.content).toContain("pages/error.astro:2:1"); }); - it("renders an error page when showErrorPage is true", async () => { + it("throws an error when hmr.overlay is false", async () => { const { render } = await fixture.startDevServer({ - showErrorPage: true, - config: { logLevel: "silent" }, - }); - - const response = await render({ - component: "error", - props: {}, - context: {}, - partial: false, - }); - expect(response.status).toBe(500); - expect(response.content).toContain("Error"); - expect(response.content).toContain( - '', - ); - expect(response.content).toContain("pages/error.astro:2:1"); - }); - - it("throws an error when showErrorPage is false", async () => { - const { render } = await fixture.startDevServer({ - showErrorPage: false, - config: { logLevel: "silent" }, + config: { + logLevel: "silent", + vite: { server: { hmr: { overlay: false } } }, + }, }); await expect( diff --git a/packages/core/tests/fixture.ts b/packages/core/tests/fixture.ts index 30beba2..482cd14 100644 --- a/packages/core/tests/fixture.ts +++ b/packages/core/tests/fixture.ts @@ -6,11 +6,7 @@ import { toFetchResponse, toReqRes } from "fetch-to-node"; import { build } from "@/build"; import { setupDev } from "@/dev/setup"; import { setupStart } from "@/start/setup"; -import type { - MightyDevOptions, - MightyRenderRequest, - MightyServerOptions, -} from "@/types"; +import type { MightyRenderRequest, MightyServerOptions } from "@/types"; import { dotStringToPath } from "@/utils/dotStringToPath"; export type DevRenderFunction = ( @@ -27,7 +23,7 @@ export type GetFromViteMiddlewareFunction = ( export function getFixture(fixtureName: string): { fixtureRoot: string; outDir: string; - startDevServer: (params?: MightyDevOptions) => Promise<{ + startDevServer: (params?: MightyServerOptions) => Promise<{ render: DevRenderFunction; getFromViteMiddleware: GetFromViteMiddlewareFunction; stop: () => Promise; diff --git a/packages/hono/src/dev.ts b/packages/hono/src/dev.ts index 1f1ebc3..ea285d2 100644 --- a/packages/hono/src/dev.ts +++ b/packages/hono/src/dev.ts @@ -1,4 +1,4 @@ -import { dev, type MightyDevOptions } from "@gomighty/core"; +import { dev, type MightyServerOptions } from "@gomighty/core"; import { mergeConfig } from "astro/config"; import { createMiddleware } from "hono/factory"; import type { UnofficialStatusCode } from "hono/utils/http-status"; @@ -8,9 +8,9 @@ import { runConnectMiddleware } from "./utils/runConnectMiddleware.ts"; const MIGHTY_DEV_ROOT = "/__MIGHTY_DEV_ADDRESS__"; export function devMiddleware( - options?: MightyDevOptions, + options?: MightyServerOptions, ): StartMightyServerMiddlewareHandler { - const mightyConfig: MightyDevOptions = { + const mightyConfig: MightyServerOptions = { ...options, config: mergeConfig( { root: "./astro", vite: { base: MIGHTY_DEV_ROOT } }, @@ -23,7 +23,11 @@ export function devMiddleware( return createMiddleware(async (c, next) => { const { render, viteMiddleware } = await setupDevPromise; - if (c.req.method === "GET" && c.req.path.includes(MIGHTY_DEV_ROOT)) { + if ( + c.req.method === "GET" && + (c.req.path.includes(MIGHTY_DEV_ROOT) || + c.req.path === "/__open-in-editor") + ) { return runConnectMiddleware(viteMiddleware, c); }