From 3d7d63aa8a106b2af0474c74ec3a047c66c9ec7b Mon Sep 17 00:00:00 2001 From: Drew Powers <1369770+drwpow@users.noreply.github.com> Date: Thu, 11 Nov 2021 13:04:57 -0700 Subject: [PATCH] Improve WASM panic error (#1782) * Improve WASM panic error * Add panic test --- packages/astro/src/core/dev/index.ts | 4 +-- packages/astro/src/core/dev/template/error.ts | 28 ++++++++++++++----- packages/astro/src/core/ssr/index.ts | 3 +- packages/astro/src/vite-plugin-astro/index.ts | 28 ++++++++++++++++++- .../wasm-panic-error/src/pages/index.astro | 3 ++ packages/astro/test/wasm-panic-error.test.js | 26 +++++++++++++++++ 6 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 packages/astro/test/fixtures/wasm-panic-error/src/pages/index.astro create mode 100644 packages/astro/test/wasm-panic-error.test.js diff --git a/packages/astro/src/core/dev/index.ts b/packages/astro/src/core/dev/index.ts index 572ab72c3..756e2a8ca 100644 --- a/packages/astro/src/core/dev/index.ts +++ b/packages/astro/src/core/dev/index.ts @@ -178,7 +178,6 @@ export class AstroDevServer { return []; } catch (e) { const err = e as Error; - viteServer.ssrFixStacktrace(err); // eslint-disable-next-line console.error(err.stack); viteServer.ws.send({ @@ -302,7 +301,6 @@ export class AstroDevServer { res.write(html); res.end(); } catch (err: any) { - this.viteServer.ssrFixStacktrace(err); this.viteServer.ws.send({ type: 'error', err }); const statusCode = 500; const html = errorTemplate({ @@ -310,6 +308,8 @@ export class AstroDevServer { title: 'Internal Error', tabTitle: '500: Error', message: stripAnsi(err.message), + url: err.url || undefined, + stack: stripAnsi(err.stack), }); info(this.logging, 'astro', msg.req({ url: pathname, statusCode: 500, reqTime: performance.now() - reqStart })); res.writeHead(statusCode, { diff --git a/packages/astro/src/core/dev/template/error.ts b/packages/astro/src/core/dev/template/error.ts index 7060e93a0..7f611328c 100644 --- a/packages/astro/src/core/dev/template/error.ts +++ b/packages/astro/src/core/dev/template/error.ts @@ -1,14 +1,23 @@ import { encode } from 'html-entities'; interface ErrorTemplateOptions { - statusCode?: number; - tabTitle: string; - title: string; + /** a short description of the error */ message: string; + /** information about where the error occurred */ + stack?: string; + /** HTTP error code */ + statusCode?: number; + /** HTML */ + tabTitle: string; + /** page title */ + title: string; + /** show user a URL for more info or action to take */ + url?: string; } /** Display internal 404 page (if user didn’t provide one) */ -export function errorTemplate({ title, message, statusCode, tabTitle }: ErrorTemplateOptions): string { +export function errorTemplate({ title, url, message, stack, statusCode, tabTitle }: ErrorTemplateOptions): string { + let error = url ? message.replace(url, '') : message; return `<!doctype html> <html lang="en"> <head> @@ -19,13 +28,16 @@ export function errorTemplate({ title, message, statusCode, tabTitle }: ErrorTem background-color: #101010; color: #d0d0d0; font-family: monospace; - line-height: 1.6; + line-height: 1.5; margin: 0; } .wrapper { padding-left: 2rem; padding-right: 2rem; } + a { + color: #ff5d01; + } h1 { font-weight: 800; margin-top: 1rem; @@ -33,7 +45,7 @@ export function errorTemplate({ title, message, statusCode, tabTitle }: ErrorTem } pre { color: #999; - font-size: 1.4em; + font-size: 1.2em; margin-top: 0; max-width: 60em; } @@ -45,7 +57,9 @@ export function errorTemplate({ title, message, statusCode, tabTitle }: ErrorTem <body> <main class="wrapper"> <h1>${statusCode ? `<span class="statusCode">${statusCode}</span> ` : ''}${title}</h1> - <pre><code>${encode(message)}</code></pre> + <pre><code>${encode(error)}</code></pre> + ${url ? `<a target="_blank" href="${url}">${url}</a>` : ''} + <pre><code>${encode(stack)}</code></pre> </main> </body> </html> diff --git a/packages/astro/src/core/ssr/index.ts b/packages/astro/src/core/ssr/index.ts index 598497144..720acbad6 100644 --- a/packages/astro/src/core/ssr/index.ts +++ b/packages/astro/src/core/ssr/index.ts @@ -81,7 +81,7 @@ async function errorHandler(e: unknown, viteServer: vite.ViteDevServer, filePath const anyError = e as any; if (anyError.errors) { const { location, pluginName, text } = (e as BuildResult).errors[0]; - const err = new Error(text) as SSRError; + const err = e as SSRError; if (location) err.loc = { file: location.file, line: location.line, column: location.column }; const frame = codeFrame(await fs.promises.readFile(filePath, 'utf8'), err.loc); err.frame = frame; @@ -89,7 +89,6 @@ async function errorHandler(e: unknown, viteServer: vite.ViteDevServer, filePath err.message = `${location?.file}: ${text} ${frame} `; - err.stack = anyError.stack; if (pluginName) err.plugin = pluginName; throw err; } diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index 8e68d9504..891c88996 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -94,7 +94,32 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P map, }; } catch (err: any) { - // if esbuild threw the error, find original code source to display (if it’s mapped) + // improve compiler errors + if (err.stack.includes('wasm-function')) { + const search = new URLSearchParams({ + labels: 'compiler', + title: '🐛 BUG: `@astrojs/compiler` panic', + body: `### Describe the Bug + +\`@astrojs/compiler\` encountered an unrecoverable error when compiling the following file. + +**${id.replace(fileURLToPath(config.projectRoot), '')}** +\`\`\`astro +${source} +\`\`\` +`, + }); + err.url = `https://github.com/snowpackjs/astro/issues/new?${search.toString()}`; + err.message = `Error: Uh oh, the Astro compiler encountered an unrecoverable error! + +Please open +a GitHub issue using the link below: +${err.url}`; + // TODO: remove stack replacement when compiler throws better errors + err.stack = ` at ${id}`; + } + + // improve esbuild errors if (err.errors && tsResult?.map) { const json = JSON.parse(tsResult.map); const mappings = decode(json.mappings); @@ -103,6 +128,7 @@ export default function astro({ config, devServer }: AstroPluginOptions): vite.P err.sourceLoc = { file: id, line: (focusMapping[0][2] || 0) + 1, column: (focusMapping[0][3] || 0) + 1 }; } } + throw err; } }, diff --git a/packages/astro/test/fixtures/wasm-panic-error/src/pages/index.astro b/packages/astro/test/fixtures/wasm-panic-error/src/pages/index.astro new file mode 100644 index 000000000..fb2847eed --- /dev/null +++ b/packages/astro/test/fixtures/wasm-panic-error/src/pages/index.astro @@ -0,0 +1,3 @@ + +<!-- bad code --> +{[...new Array(20)].map(() => (<div>))} diff --git a/packages/astro/test/wasm-panic-error.test.js b/packages/astro/test/wasm-panic-error.test.js new file mode 100644 index 000000000..4ee1e3479 --- /dev/null +++ b/packages/astro/test/wasm-panic-error.test.js @@ -0,0 +1,26 @@ +import { expect } from 'chai'; +import cheerio from 'cheerio'; +import { loadFixture } from './test-utils.js'; + +let fixture; + +before(async () => { + fixture = await loadFixture({ projectRoot: './fixtures/wasm-panic-error' }); +}); + +describe('compiler error', () => { + it('throws helpful error', async () => { + try { + await fixture.build(); + + // should err + expect(true).to.be.false; + } catch (err) { + // test err thrown contains filepath + expect(err.stack).to.include('wasm-panic-error/src/pages/index.astro'); + + // test err thrown contains "unrecoverable error" + expect(err.message || err.toString()).to.include('Uh oh, the Astro compiler encountered an unrecoverable error!'); + } + }); +});