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:
Happydev 2023-06-09 20:21:32 +00:00 committed by GitHub
parent e138f66344
commit bc7c164bc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 90 additions and 1 deletions

View file

@ -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,

View file

@ -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);`;

View file

@ -0,0 +1,7 @@
---
title: Hello, World
---
# {A.VALID.JAVASCRIPT.EXPRESSION.THAT.RESULTS.IN.A.RUNTIME.ERROR}
Invalid content in the frontmatter

View 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/,
);
});
});
});

View file

@ -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':