Do not silence runtime MDX errors (#7089)
* test: add fixture * chore: remove unneeded files in fixture * test: update test mdx filfe * test: add test case * mark component as mdx * add error builder utility * throw error when it comes from an MDX component * chore: update lock file * Add comment to refactor later * apply review * add missing comma
This commit is contained in:
parent
e138f66344
commit
bc7c164bc3
5 changed files with 90 additions and 1 deletions
|
@ -17,7 +17,20 @@ export async function check(
|
||||||
try {
|
try {
|
||||||
const result = await Component({ ...props, ...slots, children });
|
const result = await Component({ ...props, ...slots, children });
|
||||||
return result[AstroJSX];
|
return result[AstroJSX];
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
|
const error = e as Error;
|
||||||
|
// if the exception is from an mdx component
|
||||||
|
// throw an error
|
||||||
|
if (Component[Symbol.for('mdx-component')]) {
|
||||||
|
throw createFormattedError({
|
||||||
|
message: error.message,
|
||||||
|
title: error.name,
|
||||||
|
hint: `This issue often occurs when your MDX component encounters runtime errors.`,
|
||||||
|
name: error.name,
|
||||||
|
stack: error.stack,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +51,23 @@ export async function renderToStaticMarkup(
|
||||||
return { html };
|
return { html };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FormatErrorOptions = {
|
||||||
|
message: string;
|
||||||
|
name: string;
|
||||||
|
stack?: string;
|
||||||
|
hint: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
// TODO: Remove this function and use `AstroError` when we refactor it to be usable without error codes
|
||||||
|
function createFormattedError({ message, name, stack, hint }: FormatErrorOptions) {
|
||||||
|
const error = new Error(message);
|
||||||
|
error.name = name;
|
||||||
|
error.stack = stack;
|
||||||
|
// @ts-expect-error - hint is not part of the Error interface but it will be picked up by the error overlay
|
||||||
|
error.hint = hint;
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
check,
|
check,
|
||||||
renderToStaticMarkup,
|
renderToStaticMarkup,
|
||||||
|
|
|
@ -165,6 +165,9 @@ export default function mdx(partialMdxOptions: Partial<MdxOptions> = {}): AstroI
|
||||||
export default Content;`;
|
export default Content;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark the component as an MDX component
|
||||||
|
code += `\nContent[Symbol.for('mdx-component')] = true`;
|
||||||
|
|
||||||
// Ensures styles and scripts are injected into a `<head>`
|
// Ensures styles and scripts are injected into a `<head>`
|
||||||
// When a layout is not applied
|
// When a layout is not applied
|
||||||
code += `\nContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
|
code += `\nContent[Symbol.for('astro.needsHeadRendering')] = !Boolean(frontmatter.layout);`;
|
||||||
|
|
7
packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx
vendored
Normal file
7
packages/integrations/mdx/test/fixtures/invalid-mdx-component/src/pages/invalid-content.mdx
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
title: Hello, World
|
||||||
|
---
|
||||||
|
|
||||||
|
# {A.VALID.JAVASCRIPT.EXPRESSION.THAT.RESULTS.IN.A.RUNTIME.ERROR}
|
||||||
|
|
||||||
|
Invalid content in the frontmatter
|
40
packages/integrations/mdx/test/invalid-mdx-component.test.js
Normal file
40
packages/integrations/mdx/test/invalid-mdx-component.test.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { expect } from "chai";
|
||||||
|
import { loadFixture } from "../../../astro/test/test-utils.js";
|
||||||
|
import mdx from "../dist/index.js";
|
||||||
|
|
||||||
|
const FIXTURE_ROOT = new URL(
|
||||||
|
"./fixtures/invalid-mdx-component/",
|
||||||
|
import.meta.url,
|
||||||
|
);
|
||||||
|
|
||||||
|
describe("MDX component with runtime error", () => {
|
||||||
|
let fixture;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({
|
||||||
|
root: FIXTURE_ROOT,
|
||||||
|
integrations: [mdx()],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("build", () => {
|
||||||
|
/** @type {Error | null} */
|
||||||
|
let error;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
error = null;
|
||||||
|
try {
|
||||||
|
await fixture.build();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Throws the right error", async () => {
|
||||||
|
expect(error).to.exist;
|
||||||
|
expect(error?.hint).to.match(
|
||||||
|
/This issue often occurs when your MDX component encounters runtime errors/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -4231,6 +4231,15 @@ importers:
|
||||||
specifier: 0.2.3
|
specifier: 0.2.3
|
||||||
version: 0.2.3
|
version: 0.2.3
|
||||||
|
|
||||||
|
packages/integrations/mdx/test/fixtures/invalid-mdx-component:
|
||||||
|
dependencies:
|
||||||
|
'@astrojs/mdx':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../../../astro
|
||||||
|
|
||||||
packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection:
|
packages/integrations/mdx/test/fixtures/mdx-frontmatter-injection:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/mdx':
|
'@astrojs/mdx':
|
||||||
|
|
Loading…
Reference in a new issue