Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Better prerender errors #107

Merged
merged 3 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"type": "module"
}
Comment on lines +1 to +3
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stack-trace is ESM-only and I guess Vite (at least in the version we running in this repo) transpiles the config file down to the default module type, so we get a lovely require() of ES Module ...

76 changes: 66 additions & 10 deletions package-lock.json

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

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,23 @@
"kolorist": "^1.8.0",
"magic-string": "0.30.5",
"node-html-parser": "^6.1.10",
"resolve": "^1.22.8"
"resolve": "^1.22.8",
"source-map": "^0.7.4",
"stack-trace": "^1.0.0-pre2"
},
"peerDependencies": {
"@babel/core": "7.x",
"vite": "2.x || 3.x || 4.x || 5.x"
},
"devDependencies": {
"@babel/core": "^7.15.8",
"@types/babel__code-frame": "^7.0.6",
"@types/babel__core": "^7.1.14",
"@types/debug": "^4.1.5",
"@types/estree": "^0.0.50",
"@types/node": "^14.14.33",
"@types/resolve": "^1.20.1",
"@types/stack-trace": "^0.0.33",
"lint-staged": "^10.5.4",
"preact": "^10.19.2",
"preact-iso": "^2.3.2",
Expand Down
56 changes: 50 additions & 6 deletions src/prerender.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import path from "node:path";

import { promises as fs } from "node:fs";

import MagicString from "magic-string";
import { parse as htmlParse } from "node-html-parser";
import { SourceMapConsumer } from "source-map";
import { parse as StackTraceParse } from "stack-trace";
import { codeFrameColumns } from "@babel/code-frame";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally using simple-code-frame, but Babel here comes for free as we're already using it for transpiling and whatnot.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah babel's code frame also supports basic syntax highlighting which is pretty nice!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, well unfortunately that's not usable here. Not sure if it's Rollup or Vite, but the entire message is a flat red.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temp


import type { Plugin, ResolvedConfig } from "vite";

Expand Down Expand Up @@ -116,6 +118,9 @@ export function PrerenderPlugin({
apply: "build",
enforce: "post",
configResolved(config) {
// Enable sourcemaps for generating more actionable error messages
config.build.sourcemap = true;

viteConfig = config;
},
async options(opts) {
Expand Down Expand Up @@ -212,7 +217,7 @@ export function PrerenderPlugin({
JSON.stringify({ type: "module" }),
);

let prerenderEntry;
let prerenderEntry: OutputChunk | undefined;
for (const output of Object.keys(bundle)) {
if (!/\.js$/.test(output) || bundle[output].type !== "chunk") continue;

Expand All @@ -222,7 +227,7 @@ export function PrerenderPlugin({
);

if ((bundle[output] as OutputChunk).exports?.includes("prerender")) {
prerenderEntry = bundle[output];
prerenderEntry = bundle[output] as OutputChunk;
}
}
if (!prerenderEntry) {
Expand All @@ -238,22 +243,61 @@ export function PrerenderPlugin({
);
prerender = m.prerender;
} catch (e) {
const isReferenceError = e instanceof ReferenceError;
const stack = StackTraceParse(e as Error).find(s =>
s.getFileName().includes(tmpDir),
);

const message = `
const isReferenceError = e instanceof ReferenceError;
let message = `\n
${e}

This ${
isReferenceError ? "is most likely" : "could be"
} caused by using DOM/Web APIs which are not available
available to the prerendering process which runs in Node. Consider
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, "");

const sourceMapContent = prerenderEntry.map;
if (stack && sourceMapContent) {
await SourceMapConsumer.with(
sourceMapContent,
null,
async consumer => {
let { source, line, column } = consumer.originalPositionFor({
line: stack.getLineNumber(),
column: stack.getColumnNumber(),
});

if (!source || line == null || column == null) {
message += `\nUnable to locate source map for error!\n`;
this.error(message);
}

// `source-map` returns 0-indexed column numbers
column += 1;

const sourcePath = path.join(
viteConfig.root,
source.replace(/^(..\/)*/, ""),
);
const sourceContent = await fs.readFile(sourcePath, "utf-8");

const frame = codeFrameColumns(sourceContent, {
start: { line, column },
});
message += `
> ${sourcePath}:${line}:${column}\n
${frame}
`.replace(/^\t{7}/gm, "");
},
);
}

this.error(message);
}

Expand Down
Loading