-
-
Notifications
You must be signed in to change notification settings - Fork 27
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"type": "module" | ||
} | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was originally using There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
||
import type { Plugin, ResolvedConfig } from "vite"; | ||
|
||
|
@@ -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) { | ||
|
@@ -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; | ||
|
||
|
@@ -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) { | ||
|
@@ -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); | ||
} | ||
|
||
|
There was a problem hiding this comment.
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 lovelyrequire() of ES Module ...