Skip to content

Commit

Permalink
Prevent throwing in react and solid component checks (#11624)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy authored Aug 9, 2024
1 parent cc405dd commit 7adb350
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-starfishes-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/solid-js': patch
---

Prevents throwing errors when checking if a component is a Solid component in runtime
5 changes: 5 additions & 0 deletions .changeset/spotty-lies-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/react': patch
---

Prevents throwing errors when checking if a component is a React component in runtime
7 changes: 5 additions & 2 deletions packages/astro/e2e/errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,18 @@ test.describe('Error display', () => {
expect(fileLocation).toMatch(/^pages\/import-not-found\.astro/);
});

// NOTE: It's not possible to detect some JSX components if they have errors because
// their renderers' check functions run the render directly, and if a runtime error is
// thrown, it assumes that it's simply not that renderer's component and skips it
test('shows correct file path when a component has an error', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/preact-runtime-error'), { waitUntil: 'networkidle' });
await page.goto(astro.resolveUrl('/vue-runtime-error'), { waitUntil: 'networkidle' });

const { fileLocation, absoluteFileLocation } = await getErrorOverlayContent(page);
const absoluteFileUrl = 'file://' + absoluteFileLocation.replace(/:\d+:\d+$/, '');
const fileExists = astro.pathExists(absoluteFileUrl);

expect(fileExists).toBeTruthy();
expect(fileLocation).toMatch(/^preact\/PreactRuntimeError.jsx/);
expect(fileLocation).toMatch(/^vue\/VueRuntimeError.vue/);
});

test('shows correct line when a style preprocess has an error', async ({ page, astro }) => {
Expand Down
20 changes: 9 additions & 11 deletions packages/integrations/preact/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,17 @@ async function check(
useConsoleFilter();

try {
try {
const { html } = await renderToStaticMarkup.call(this, Component, props, children, undefined);
if (typeof html !== 'string') {
return false;
}

// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>
// It also might render an empty sting.
return html == '' ? false : !html.includes('<undefined>');
} catch {
const { html } = await renderToStaticMarkup.call(this, Component, props, children, undefined);
if (typeof html !== 'string') {
return false;
}

// There are edge cases (SolidJS) where Preact *might* render a string,
// but components would be <undefined></undefined>
// It also might render an empty sting.
return html == '' ? false : !html.includes('<undefined>');
} catch {
return false;
} finally {
finishUsingConsoleFilter();
}
Expand Down
18 changes: 1 addition & 17 deletions packages/integrations/react/server-v17.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ import StaticHtml from './static-html.js';
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');

function errorIsComingFromPreactComponent(err) {
return (
err.message &&
(err.message.startsWith("Cannot read property '__H'") ||
err.message.includes("(reading '__H')"))
);
}

function check(Component, props, children) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
Expand All @@ -26,28 +18,20 @@ function check(Component, props, children) {
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
}

let error = null;
let isReactComponent = false;
function Tester(...args) {
try {
const vnode = Component(...args);
if (vnode && vnode['$$typeof'] === reactTypeof) {
isReactComponent = true;
}
} catch (err) {
if (!errorIsComingFromPreactComponent(err)) {
error = err;
}
}
} catch {}

return React.createElement('div');
}

renderToStaticMarkup(Tester, props, children, {});

if (error) {
throw error;
}
return isReactComponent;
}

Expand Down
18 changes: 1 addition & 17 deletions packages/integrations/react/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@ import StaticHtml from './static-html.js';
const slotName = (str) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase());
const reactTypeof = Symbol.for('react.element');

function errorIsComingFromPreactComponent(err) {
return (
err.message &&
(err.message.startsWith("Cannot read property '__H'") ||
err.message.includes("(reading '__H')"))
);
}

async function check(Component, props, children) {
// Note: there are packages that do some unholy things to create "components".
// Checking the $$typeof property catches most of these patterns.
Expand All @@ -32,28 +24,20 @@ async function check(Component, props, children) {
return React.Component.isPrototypeOf(Component) || React.PureComponent.isPrototypeOf(Component);
}

let error = null;
let isReactComponent = false;
function Tester(...args) {
try {
const vnode = Component(...args);
if (vnode && vnode['$$typeof'] === reactTypeof) {
isReactComponent = true;
}
} catch (err) {
if (!errorIsComingFromPreactComponent(err)) {
error = err;
}
}
} catch {}

return React.createElement('div');
}

await renderToStaticMarkup(Tester, props, children, {});

if (error) {
throw error;
}
return isReactComponent;
}

Expand Down
16 changes: 10 additions & 6 deletions packages/integrations/solid/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ async function check(
// In general, components from other frameworks (eg, MDX, React, etc.) tend to render as "undefined",
// so we take advantage of this trick to decide if this is a Solid component or not.

const { html } = await renderToStaticMarkup.call(this, Component, props, children, {
// The purpose of check() is just to validate that this is a Solid component and not
// React, etc. We should render in sync mode which should skip Suspense boundaries
// or loading resources like external API calls.
renderStrategy: 'sync' as RenderStrategy,
});
let html: string | undefined;
try {
const result = await renderToStaticMarkup.call(this, Component, props, children, {
// The purpose of check() is just to validate that this is a Solid component and not
// React, etc. We should render in sync mode which should skip Suspense boundaries
// or loading resources like external API calls.
renderStrategy: 'sync' as RenderStrategy,
});
html = result.html;
} catch {}

return typeof html === 'string';
}
Expand Down

0 comments on commit 7adb350

Please sign in to comment.