From 8d059faaedf212426e0fb6d93843f6855f723f56 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Tue, 20 Sep 2022 09:38:17 -0400 Subject: [PATCH] Prevent errors in React components from crashing the dev server (#4816) * Prevent errors in React components from crashing the dev server * Add a changeset * Fix test when running in the build --- .changeset/tricky-walls-walk.md | 5 +++++ .../src/components/ImportsThrowsAnError.jsx | 7 +++++++ .../src/components/ThrowsAnError.jsx | 15 +++++++++++++++ .../src/pages/error-rendering.astro | 11 +++++++++++ packages/astro/test/react-component.test.js | 6 ++++++ packages/integrations/react/server.js | 5 ++++- 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .changeset/tricky-walls-walk.md create mode 100644 packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx create mode 100644 packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx create mode 100644 packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro diff --git a/.changeset/tricky-walls-walk.md b/.changeset/tricky-walls-walk.md new file mode 100644 index 000000000..9e58ec014 --- /dev/null +++ b/.changeset/tricky-walls-walk.md @@ -0,0 +1,5 @@ +--- +'@astrojs/react': patch +--- + +Prevent errors in React components from crashing the dev server diff --git a/packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx b/packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx new file mode 100644 index 000000000..d6ff21dc3 --- /dev/null +++ b/packages/astro/test/fixtures/react-component/src/components/ImportsThrowsAnError.jsx @@ -0,0 +1,7 @@ +import ThrowsAnError from "./ThrowsAnError"; + +export default function() { + return <> + + +} diff --git a/packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx b/packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx new file mode 100644 index 000000000..cf970e38c --- /dev/null +++ b/packages/astro/test/fixtures/react-component/src/components/ThrowsAnError.jsx @@ -0,0 +1,15 @@ +import { useState } from 'react'; + +export default function() { + let player = undefined; + // This is tested in dev mode, so make it work during the build to prevent + // breaking other tests. + if(import.meta.env.MODE === 'production') { + player = {}; + } + const [] = useState(player.currentTime || null); + + return ( +
Should have thrown
+ ) +} diff --git a/packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro b/packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro new file mode 100644 index 000000000..6984a6da5 --- /dev/null +++ b/packages/astro/test/fixtures/react-component/src/pages/error-rendering.astro @@ -0,0 +1,11 @@ +--- +import ImportsThrowsAnError from '../components/ImportsThrowsAnError'; +--- + + + Testing + + + + + diff --git a/packages/astro/test/react-component.test.js b/packages/astro/test/react-component.test.js index e18f7129c..19bd42a43 100644 --- a/packages/astro/test/react-component.test.js +++ b/packages/astro/test/react-component.test.js @@ -94,6 +94,7 @@ describe('React Components', () => { if (isWindows) return; describe('dev', () => { + /** @type {import('./test-utils').Fixture} */ let devServer; before(async () => { @@ -145,5 +146,10 @@ describe('React Components', () => { // test 1: react/jsx-runtime is used for the component expect(jsxRuntime).to.be.ok; }); + + it('When a nested component throws it does not crash the server', async () => { + const res = await fixture.fetch('/error-rendering'); + await res.arrayBuffer(); + }); }); }); diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index c4e77bb91..d8c0c25fb 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -121,8 +121,11 @@ async function renderToPipeableStreamAsync(vnode) { async function renderToStaticNodeStreamAsync(vnode) { const Writable = await getNodeWritable(); let html = ''; - return new Promise((resolve) => { + return new Promise((resolve, reject) => { let stream = ReactDOM.renderToStaticNodeStream(vnode); + stream.on('error', err => { + reject(err); + }); stream.pipe( new Writable({ write(chunk, _encoding, callback) {