Skip to content

Commit

Permalink
Merge pull request #852 from preactjs/prerender-error
Browse files Browse the repository at this point in the history
Add code frame to uncaught exceptions if possible
  • Loading branch information
marvinhagemeister authored Sep 6, 2021
2 parents cdcbded + bdc4a54 commit 2c8521c
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/early-carrots-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'wmr': patch
---

Add code frame to uncaught exceptions if possible
2 changes: 1 addition & 1 deletion packages/wmr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"sade": "^1.7.3",
"sass": "^1.34.1",
"semver": "^7.3.2",
"simple-code-frame": "^1.1.1",
"simple-code-frame": "^1.2.0",
"sirv": "^1.0.6",
"sourcemap-codec": "^1.4.8",
"stylis": "^4.0.10",
Expand Down
21 changes: 14 additions & 7 deletions packages/wmr/src/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@ export default async function build(options) {

if (!options.prerender) return;

const { routes } = await prerender(options);
const routeMap = routes.reduce((s, r) => {
s += `\n ${r.url}`;
if (r._discoveredBy) s += kl.dim(` [from ${r._discoveredBy.url}]`);
return s;
}, '');
process.stdout.write(kl.bold(`Prerendered ${routes.length} page${routes.length == 1 ? '' : 's'}:`) + routeMap + '\n');
try {
const { routes } = await prerender(options);
const routeMap = routes.reduce((s, r) => {
s += `\n ${r.url}`;
if (r._discoveredBy) s += kl.dim(` [from ${r._discoveredBy.url}]`);
return s;
}, '');
process.stdout.write(
kl.bold(`Prerendered ${routes.length} page${routes.length == 1 ? '' : 's'}:`) + routeMap + '\n'
);
} catch (err) {
err.hint = 'The following error was thrown during prerendering:';
throw err;
}
}
23 changes: 21 additions & 2 deletions packages/wmr/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import start from './start.js';
import serve from './serve.js';
import * as errorstacks from 'errorstacks';
import * as kl from 'kolorist';
import { fileURLToPath } from 'url';
import { promises as fs } from 'fs';
import { wmrCodeFrame } from './lib/output-utils.js';

const prog = sade('wmr');

Expand Down Expand Up @@ -73,21 +76,37 @@ function run(p) {
/**
* @param {Error} err
*/
function catchException(err) {
async function catchException(err) {
let text = '';
let stack = '';
let codeFrame = '';
if (err.stack) {
const formattedStack = errorstacks.parseStackTrace(err.stack);
if (formattedStack.length > 0) {
const idx = err.stack.indexOf(formattedStack[0].raw);
text = err.stack.slice(0, idx).trim() + '\n';
stack = formattedStack.map(frame => frame.raw).join('\n');

// Find first non-internal frame of the stack
const frame = formattedStack.find(frame => !frame.fileName.startsWith('node:') && frame.type !== 'native');
if (frame) {
let file = frame.fileName;
file = file.startsWith('file://') ? fileURLToPath(file) : file;
try {
const code = await fs.readFile(file, 'utf-8');
codeFrame = wmrCodeFrame(code, frame.line - 1, frame.column);
} catch (err) {}
}
}
}

if (!text) text = err.message || err + '';

process.stderr.write(`\n${kl.red(text)}\n${stack ? kl.dim(stack + '\n\n') : ''}`);
const printFrame = codeFrame ? codeFrame + '\n' : '';
const printStack = stack ? kl.dim(stack + '\n\n') : '';

const hint = err.hint ? err.hint + '\n\n' : '';
process.stderr.write(`\n${kl.cyan(hint)}${kl.red(text)}${printFrame || '\n'}${printStack}`);
process.exit(1);
}

Expand Down
6 changes: 5 additions & 1 deletion packages/wmr/src/lib/output-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { createCodeFrame } from 'simple-code-frame';
import * as util from 'util';
import path from 'path';

export function wmrCodeFrame(code, line, column, options = {}) {
return createCodeFrame(code, line, column, { maxWidth: Math.min(80, process.stdout.columns - 4), ...options });
}

/**
* @param {import('rollup').RollupOutput} bundle
* @param {string} outDir
Expand Down Expand Up @@ -105,7 +109,7 @@ export function codeFrame(code, loc, { before = 2, after = 3 } = {}) {
({ line, column } = loc);
}

return createCodeFrame(code, line - 1, column, { before, after, colors: true });
return wmrCodeFrame(code, line - 1, column, { before, after, colors: true });
}

// Taken from https://github.com/visionmedia/debug/blob/e47f96de3de5921584364b4ac91e2769d22a3b1f/src/node.js#L35
Expand Down
4 changes: 2 additions & 2 deletions packages/wmr/src/plugins/less-plugin.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { createCodeFrame } from 'simple-code-frame';
import { resolveAlias } from '../lib/aliasing.js';
import { isFile } from '../lib/fs-utils.js';
import { wmrCodeFrame } from '../lib/output-utils.js';

/** @type {import('less') | undefined} */
let less;
Expand Down Expand Up @@ -71,7 +71,7 @@ export async function renderLess(code, { id, resolve, sourcemap }) {
} catch (err) {
if (err.extract && 'line' in err && 'column' in err) {
const code = err.extract.filter(l => l !== undefined).join('\n');
err.codeFrame = createCodeFrame(code, err.line - 1, err.column);
err.codeFrame = wmrCodeFrame(code, err.line - 1, err.column);
}

throw err;
Expand Down
5 changes: 2 additions & 3 deletions packages/wmr/src/plugins/sass-plugin.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import path from 'path';
import { promisify } from 'util';
import { debug } from '../lib/output-utils.js';
import { wmrCodeFrame, debug } from '../lib/output-utils.js';
import * as kl from 'kolorist';
import { promises as fs } from 'fs';
import { createCodeFrame } from 'simple-code-frame';

const log = debug('sass');

Expand Down Expand Up @@ -120,7 +119,7 @@ export default function sassPlugin({ production, sourcemap, root, mergedAssets }
async function handleError(err) {
if (err.file) {
const code = await fs.readFile(err.file, 'utf-8');
err.codeFrame = createCodeFrame(code, err.line - 1, err.column);
err.codeFrame = wmrCodeFrame(code, err.line - 1, err.column);
}
// Sass mixes stack in message, therefore we need to extract
// just the message
Expand Down
4 changes: 2 additions & 2 deletions packages/wmr/src/plugins/sucrase-plugin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as sucrase from 'sucrase';
import { createCodeFrame } from 'simple-code-frame';
import { wmrCodeFrame } from '../lib/output-utils.js';

const cjsDefault = m => ('default' in m ? m.default : m);

Expand Down Expand Up @@ -68,7 +68,7 @@ export default function sucrasePlugin(opts = {}) {
};
} catch (err) {
// Enhance error with code frame
err.codeFrame = createCodeFrame(code, err.loc.line - 1, err.loc.column);
err.codeFrame = wmrCodeFrame(code, err.loc.line - 1, err.loc.column);
throw err;
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/wmr/src/plugins/wmr/styles/styles-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { transformCssImports } from '../../../lib/transform-css-imports.js';
import { transformCss } from '../../../lib/transform-css.js';
import { matchAlias } from '../../../lib/aliasing.js';
import { modularizeCss } from './css-modules.js';
import { createCodeFrame } from 'simple-code-frame';
import { wmrCodeFrame } from '../../../lib/output-utils.js';

export const STYLE_REG = /\.(?:css|s[ac]ss|less)$/;

Expand Down Expand Up @@ -48,7 +48,7 @@ export default function wmrStylesPlugin({ root, hot, production, alias, sourcema
const lines = source.slice(0, match.index).split('\n');
const line = lines.length - 1;
const column = lines[lines.length - 1].length;
const codeFrame = createCodeFrame(source, line, column);
const codeFrame = wmrCodeFrame(source, line, column);

const originalName = basename(idRelative);
const nameHint = basename(idRelative, extname(idRelative)) + '.module' + extname(idRelative);
Expand Down
10 changes: 10 additions & 0 deletions packages/wmr/test/fixtures/prerender-error/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf8" />
<title>default title</title>
</head>
<body>
<script src="./index.js" type="module"></script>
</body>
</html>
3 changes: 3 additions & 0 deletions packages/wmr/test/fixtures/prerender-error/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function prerender() {
return window.foo.bar;
}
11 changes: 11 additions & 0 deletions packages/wmr/test/production.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,17 @@ describe('production', () => {
expect(instance.output.join('\n')).toMatch(/No prerender\(\) function/i);
});
});

it('should catch uncaught exceptions during prerendering', async () => {
await loadFixture('prerender-error', env);
instance = await runWmr(env.tmp.path, 'build', '--prerender');
const code = await instance.done;

await withLog(instance.output, async () => {
expect(code).toBe(1);
expect(instance.output.join('\n')).toMatch(/The following error was thrown during prerendering/i);
});
});
});

describe('Code Splitting', () => {
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8229,10 +8229,10 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3:
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==

simple-code-frame@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/simple-code-frame/-/simple-code-frame-1.1.1.tgz#f5c87a8d68a5d9b0df95081589a19e5ac79f6c2b"
integrity sha512-Xi3wMZQScdiJbg9+nuSIp3aG5FIBd+GMvxPf9tOB1v102/yfngTgQU9aSnULgtYSGqrSqqdMOKunMy+Jhx871g==
simple-code-frame@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/simple-code-frame/-/simple-code-frame-1.2.0.tgz#91e077f306bb6b939c5b77351eb73aa647dd36fb"
integrity sha512-/c0dj+4mp7Og6SZV3vVjWAYVKW1bwq+3zsMOQqkRcIE9CTG6P5Wo/utexgT0jEMG+feTht4H9G3s6ybMlbeA8g==
dependencies:
kolorist "^1.4.0"

Expand Down

0 comments on commit 2c8521c

Please sign in to comment.