astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
124 lines (123 loc) • 4.69 kB
JavaScript
import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { codeToHtml, createCssVariablesTheme } from "shiki";
import { AstroError } from "../errors.js";
import { FailedToLoadModuleSSR, MdxIntegrationMissingError } from "../errors-data.js";
import { createSafeError } from "../utils.js";
import { getDocsForError, renderErrorMarkdown } from "./runtime.js";
export function enhanceViteSSRError({ error, filePath, loader, renderers }) {
// NOTE: We don't know where the error that's coming here comes from, so we need to be defensive regarding what we do
// to it to make sure we keep as much information as possible. It's very possible that we receive an error that does not
// follow any kind of standard formats (ex: a number, a string etc)
let safeError = createSafeError(error);
// Vite will give you better stacktraces, using sourcemaps.
if (loader) {
try {
loader.fixStacktrace(safeError);
} catch {}
}
if (filePath) {
const path = fileURLToPath(filePath);
const content = fs.readFileSync(path).toString();
const lns = content.split("\n");
// Vite has a fairly generic error message when it fails to load a module, let's try to enhance it a bit
// https://github.com/vitejs/vite/blob/ee7c28a46a6563d54b828af42570c55f16b15d2c/packages/vite/src/node/ssr/ssrModuleLoader.ts#L91
let importName;
if (importName = /Failed to load url (.*?) \(resolved id:/.exec(safeError.message)?.[1]) {
safeError.title = FailedToLoadModuleSSR.title;
safeError.name = "FailedToLoadModuleSSR";
safeError.message = FailedToLoadModuleSSR.message(importName);
safeError.hint = FailedToLoadModuleSSR.hint;
const line = lns.findIndex((ln) => ln.includes(importName));
if (line !== -1) {
const column = lns[line]?.indexOf(importName);
safeError.loc = {
file: path,
line: line + 1,
column
};
}
}
const fileId = safeError.id ?? safeError.loc?.file;
// Vite throws a syntax error trying to parse MDX without a plugin.
// Suggest installing the MDX integration if none is found.
if (fileId && !renderers?.find((r) => r.name === "@astrojs/mdx") && safeError.message.includes("Syntax error") && /.mdx$/.test(fileId)) {
safeError = new AstroError({
...MdxIntegrationMissingError,
message: MdxIntegrationMissingError.message(JSON.stringify(fileId)),
location: safeError.loc,
stack: safeError.stack
});
}
}
return safeError;
}
// Shiki does not support `mjs` or `cjs` aliases by default.
// Map these to `.js` during error highlighting.
const ALTERNATIVE_JS_EXTS = ["cjs", "mjs"];
const ALTERNATIVE_MD_EXTS = ["mdoc"];
let _cssVariablesTheme;
const cssVariablesTheme = () => _cssVariablesTheme ?? (_cssVariablesTheme = createCssVariablesTheme({ variablePrefix: "--astro-code-" }));
/**
* Generate a payload for Vite's error overlay
*/
export async function getViteErrorPayload(err) {
let plugin = err.plugin;
if (!plugin && err.hint) {
plugin = "astro";
}
const message = renderErrorMarkdown(err.message.trim(), "html");
const hint = err.hint ? renderErrorMarkdown(err.hint.trim(), "html") : undefined;
const docslink = getDocsForError(err);
let highlighterLang = err.loc?.file?.split(".").pop();
if (ALTERNATIVE_JS_EXTS.includes(highlighterLang ?? "")) {
highlighterLang = "js";
} else if (ALTERNATIVE_MD_EXTS.includes(highlighterLang ?? "")) {
highlighterLang = "md";
}
const highlightedCode = err.fullCode ? await codeToHtml(err.fullCode, {
lang: highlighterLang ?? "text",
theme: cssVariablesTheme(),
transformers: [transformerCompactLineOptions(err.loc?.line ? [{
line: err.loc.line,
classes: ["error-line"]
}] : undefined)]
}) : undefined;
return {
__isEnhancedAstroErrorPayload: true,
type: "error",
err: {
...err,
name: err.name,
type: err.type,
message,
hint,
frame: err.frame,
highlightedCode,
docslink,
loc: {
file: err.loc?.file,
line: err.loc?.line,
column: err.loc?.column
},
plugin,
stack: err.stack,
cause: err.cause
}
};
}
/**
* Transformer for `shiki`'s legacy `lineOptions`, allows adding classes to specific lines
* FROM: https://github.com/shikijs/shiki/blob/4a58472070a9a359a4deafec23bb576a73e24c6a/packages/transformers/src/transformers/compact-line-options.ts
* LICENSE: https://github.com/shikijs/shiki/blob/4a58472070a9a359a4deafec23bb576a73e24c6a/LICENSE
*/
function transformerCompactLineOptions(lineOptions = []) {
return {
name: "@shikijs/transformers:compact-line-options",
line(node, line) {
const lineOption = lineOptions.find((o) => o.line === line);
if (lineOption?.classes) this.addClassToHast(node, lineOption.classes);
return node;
}
};
}