Skip to content

Commit

Permalink
fix: Catch non-top level prerender errors
Browse files Browse the repository at this point in the history
  • Loading branch information
rschristian committed Jan 4, 2025
1 parent 4420466 commit cb1c1eb
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 29 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
},
"devDependencies": {
"@types/node": "^20.11.16",
"dedent": "^1.5.3",
"prettier": "^2.8.8",
"prettier-config-rschristian": "^0.1.1",
"tmp-promise": "^3.0.3",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 35 additions & 25 deletions src/plugins/prerender-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
viteConfig.root,
'node_modules',
'vite-prerender-plugin',
tmpDirId
tmpDirId,
);
try {
await fs.rm(tmpDir, { recursive: true });
Expand Down Expand Up @@ -273,31 +273,21 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
this.error('Cannot detect module with `prerender` export');
}

/** @type {import('./types.d.ts').Head} */
let head = { lang: '', title: '', elements: new Set() };

let prerender;
try {
const m = await import(
`file://${path.join(tmpDir, path.basename(prerenderEntry.fileName))}`
);
prerender = m.prerender;
} catch (e) {
const handlePrerenderError = async (e) => {
const isReferenceError = e instanceof ReferenceError;

let message = `
${e}
let message = `\n
${e}
This ${
This ${
isReferenceError ? 'is most likely' : 'could be'
} caused by using DOM/Web APIs which are not available
available to the prerendering process running in Node. Consider
wrapping the offending code in a window check like so:
available to the prerendering process running in Node. Consider wrapping
the offending code in a window check like so:
if (typeof window !== "undefined") {
// do something in browsers only
}
`.replace(/^\t{5}/gm, '');
if (typeof window !== "undefined") {
// do something in browsers only
}`.replace(/^ {20}/gm, '');

const stack = StackTraceParse(e).find((s) =>
s.getFileName().includes(tmpDirId),
Expand All @@ -324,13 +314,26 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere

// `simple-code-frame` has 1-based line numbers
const frame = createCodeFrame(sourceContent, line - 1, column);
message += `
> ${sourcePath}:${line}:${column + 1}\n
${frame}
`.replace(/^\t{7}/gm, '');
message += `\n
> ${sourcePath}:${line}:${column + 1}\n
${frame}`.replace(/^ {28}/gm, '');
});
}

return message;
};

/** @type {import('./types.d.ts').Head} */
let head = { lang: '', title: '', elements: new Set() };

let prerender;
try {
const m = await import(
`file://${path.join(tmpDir, path.basename(prerenderEntry.fileName))}`
);
prerender = m.prerender;
} catch (e) {
const message = await handlePrerenderError(e);
this.error(message);
}

Expand Down Expand Up @@ -359,7 +362,14 @@ export function prerenderPlugin({ prerenderScript, renderTarget, additionalPrere
} catch {}
}

const result = await prerender({ ssr: true, url: route.url, route });
let result;
try {
result = await prerender({ ssr: true, url: route.url, route });
} catch (e) {
const message = await handlePrerenderError(e);
this.error(message);
}

if (result == null) {
this.warn(`No result returned for route: ${route.url}`);
continue;
Expand Down
29 changes: 25 additions & 4 deletions tests/source-maps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import { test } from 'uvu';
import * as assert from 'uvu/assert';
import path from 'node:path';
import { promises as fs } from 'node:fs';
import dedent from 'dedent';

import { setupTest, teardownTest, loadFixture, viteBuild } from './lib/lifecycle.js';
import { getOutputFile, outputFileExists, writeFixtureFile } from './lib/utils.js';
import { writeFixtureFile } from './lib/utils.js';

const writeConfig = async (dir, content) => writeFixtureFile(dir, 'vite.config.js', content);

Expand Down Expand Up @@ -67,9 +68,9 @@ test('Should preserve sourcemaps if user has enabled them', async () => {
assert.ok(outDirAssets.includes(outputMap));
});

test('Should use sourcemaps to display error positioning when possible', async () => {
test('Should use sourcemaps to display error positioning for top-level throws', async () => {
await loadFixture('simple', env);
await writeFixtureFile(env.tmp.path, 'src/index.js', `
await writeFixtureFile(env.tmp.path, 'src/index.js', dedent`
document.createElement('div');
export async function prerender() {
return '<h1>Simple Test Result</h1>';
Expand All @@ -84,7 +85,27 @@ test('Should use sourcemaps to display error positioning when possible', async (
}

assert.match(message, 'ReferenceError: document is not defined');
assert.match(message, 'src/index.js:2:9');
assert.match(message, 'src/index.js:1:1');
});

test('Should use sourcemaps to display error positioning for throws during prerender', async () => {
await loadFixture('simple', env);
await writeFixtureFile(env.tmp.path, 'src/index.js', dedent`
export async function prerender() {
document.createElement('div');
return '<h1>Simple Test Result</h1>';
}`,
);

let message = '';
try {
await viteBuild(env.tmp.path);
} catch (error) {
message = error.message;
}

assert.match(message, 'ReferenceError: document is not defined');
assert.match(message, 'src/index.js:2:5');
});

test.run();

0 comments on commit cb1c1eb

Please sign in to comment.