diff --git a/.changeset/orange-cycles-serve.md b/.changeset/orange-cycles-serve.md new file mode 100644 index 000000000..123593116 --- /dev/null +++ b/.changeset/orange-cycles-serve.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Makes the dev server more resilient to crashes diff --git a/packages/astro/src/core/errors.ts b/packages/astro/src/core/errors.ts index d96fbe885..9b2c7c174 100644 --- a/packages/astro/src/core/errors.ts +++ b/packages/astro/src/core/errors.ts @@ -48,7 +48,10 @@ export function cleanErrorStack(stack: string) { export function fixViteErrorMessage(_err: unknown, server?: ViteDevServer, filePath?: URL) { const err = createSafeError(_err); // Vite will give you better stacktraces, using sourcemaps. - server?.ssrFixStacktrace(err); + try { + server?.ssrFixStacktrace(err); + } catch {} + // Fix: Astro.glob() compiles to import.meta.glob() by the time Vite sees it, // so we need to update this error message in case it originally came from Astro.glob(). if (err.message === 'import.meta.glob() can only accept string literals.') { @@ -57,14 +60,16 @@ export function fixViteErrorMessage(_err: unknown, server?: ViteDevServer, fileP if (filePath && /failed to load module for ssr:/.test(err.message)) { const importName = err.message.split('for ssr:').at(1)?.trim(); if (importName) { - const content = fs.readFileSync(fileURLToPath(filePath)).toString(); - const lns = content.split('\n'); - const line = lns.findIndex((ln) => ln.includes(importName)); - if (line == -1) return err; - const column = lns[line]?.indexOf(importName); - if (!(err as any).id) { - (err as any).id = `${fileURLToPath(filePath)}:${line + 1}:${column + 1}`; - } + try { + const content = fs.readFileSync(fileURLToPath(filePath)).toString(); + const lns = content.split('\n'); + const line = lns.findIndex((ln) => ln.includes(importName)); + if (line == -1) return err; + const column = lns[line]?.indexOf(importName); + if (!(err as any).id) { + (err as any).id = `${fileURLToPath(filePath)}:${line + 1}:${column + 1}`; + } + } catch {} } } return err; diff --git a/packages/astro/src/vite-plugin-astro-server/index.ts b/packages/astro/src/vite-plugin-astro-server/index.ts index 5a550ec18..05f2b893a 100644 --- a/packages/astro/src/vite-plugin-astro-server/index.ts +++ b/packages/astro/src/vite-plugin-astro-server/index.ts @@ -348,7 +348,7 @@ async function handleRequest( return await writeSSRResult(result, res); } } catch (_err) { - const err = fixViteErrorMessage(createSafeError(_err), viteServer, filePath); + const err = fixViteErrorMessage(_err, viteServer, filePath); const errorWithMetadata = collectErrorMetadata(err); error(logging, null, msg.formatErrorMessage(errorWithMetadata)); handle500Response(viteServer, origin, req, res, errorWithMetadata); diff --git a/packages/astro/test/error-bad-js.test.js b/packages/astro/test/error-bad-js.test.js new file mode 100644 index 000000000..0f4c12e6d --- /dev/null +++ b/packages/astro/test/error-bad-js.test.js @@ -0,0 +1,34 @@ +import { expect } from 'chai'; +import * as cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +describe('Errors in JavaScript', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + + /** @type {import('./test-utils').DevServer} */ + let devServer; + + before(async () => { + fixture = await loadFixture({ + root: './fixtures/error-bad-js', + }); + devServer = await fixture.startDevServer(); + }); + + after(async() => { + await devServer.stop(); + }); + + it('Does not crash the dev server', async () => { + let res = await fixture.fetch('/'); + let html = await res.text(); + + expect(html).to.include('ReferenceError'); + + res = await fixture.fetch('/'); + await res.text(); + + expect(html).to.include('ReferenceError'); + }); +}); diff --git a/packages/astro/test/fixtures/error-bad-js/astro.config.mjs b/packages/astro/test/fixtures/error-bad-js/astro.config.mjs new file mode 100644 index 000000000..86dbfb924 --- /dev/null +++ b/packages/astro/test/fixtures/error-bad-js/astro.config.mjs @@ -0,0 +1,3 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({}); diff --git a/packages/astro/test/fixtures/error-bad-js/package.json b/packages/astro/test/fixtures/error-bad-js/package.json new file mode 100644 index 000000000..6b7af585e --- /dev/null +++ b/packages/astro/test/fixtures/error-bad-js/package.json @@ -0,0 +1,15 @@ +{ + "name": "@test/error-bad-js", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "astro": "workspace:*" + } +} diff --git a/packages/astro/test/fixtures/error-bad-js/src/env.d.ts b/packages/astro/test/fixtures/error-bad-js/src/env.d.ts new file mode 100644 index 000000000..f964fe0cf --- /dev/null +++ b/packages/astro/test/fixtures/error-bad-js/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/astro/test/fixtures/error-bad-js/src/pages/index.astro b/packages/astro/test/fixtures/error-bad-js/src/pages/index.astro new file mode 100644 index 000000000..1fbac6699 --- /dev/null +++ b/packages/astro/test/fixtures/error-bad-js/src/pages/index.astro @@ -0,0 +1,16 @@ +--- +import something from '../something.js'; +--- + + + + + + + + Astro + + +

Astro

+ + diff --git a/packages/astro/test/fixtures/error-bad-js/src/something.js b/packages/astro/test/fixtures/error-bad-js/src/something.js new file mode 100644 index 000000000..13a1c6352 --- /dev/null +++ b/packages/astro/test/fixtures/error-bad-js/src/something.js @@ -0,0 +1 @@ +export var foo = bar; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcb3c2d20..854833fcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1492,6 +1492,12 @@ importers: '@astrojs/preact': link:../../../../integrations/preact astro: link:../../.. + packages/astro/test/fixtures/error-bad-js: + specifiers: + astro: workspace:* + dependencies: + astro: link:../../.. + packages/astro/test/fixtures/fetch: specifiers: '@astrojs/preact': workspace:*