From f88fff1a241b3220f1597688e597fba3d92aceb7 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Wed, 18 May 2022 08:55:16 -0700 Subject: [PATCH] Prod can now send error messages along with a hash, however this is currently only used when the server aborts On the client, the error messages received is used or a generic fallback error message is used. In all environments the hash is attached to the error if found. In Dev, the component stack will be included as well. This gives us more options to inspect the error in onRecoverableError on the client while unifying the code paths in dev/prod --- .../src/__tests__/ReactDOMFizzServer-test.js | 143 ++++++++++++------ .../src/server/ReactDOMServerFormatConfig.js | 74 ++++----- .../src/ReactFiberBeginWork.new.js | 49 +++--- .../src/ReactFiberBeginWork.old.js | 49 +++--- .../src/forks/ReactFiberHostConfig.custom.js | 2 - packages/react-server/src/ReactFizzServer.js | 18 ++- 6 files changed, 173 insertions(+), 162 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index f574d34a1625d..3101239dedde5 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -90,24 +90,37 @@ describe('ReactDOMFizzServer', () => { }); function expectErrors(errorsArr, toBeDevArr, toBeProdArr) { + const mappedErrows = errorsArr.map(error => { + if (error.componentStack) { + return [ + error.message, + error.hash, + normalizeCodeLocInfo(error.componentStack), + ]; + } else if (error.hash) { + return [error.message, error.hash]; + } + return error.message; + }); if (__DEV__) { - expect(errorsArr.map(error => normalizeCodeLocInfo(error))).toEqual( - toBeDevArr.map(error => { - if (typeof error === 'string' || error instanceof String) { - return error; - } - let str = JSON.stringify(error).replace(/\\n/g, '\n'); - // this gets stripped away by normalizeCodeLocInfo... - // Kind of hacky but lets strip it away here too just so they match... - // easier than fixing the regex to account for this edge case - if (str.endsWith('at **)"}')) { - str = str.replace(/at \*\*\)\"}$/, 'at **)'); - } - return str; - }), + expect(mappedErrows).toEqual( + toBeDevArr, + // .map(([errorMessage, errorHash, errorComponentStack]) => { + // if (typeof error === 'string' || error instanceof String) { + // return error; + // } + // let str = JSON.stringify(error).replace(/\\n/g, '\n'); + // // this gets stripped away by normalizeCodeLocInfo... + // // Kind of hacky but lets strip it away here too just so they match... + // // easier than fixing the regex to account for this edge case + // if (str.endsWith('at **)"}')) { + // str = str.replace(/at \*\*\)\"}$/, 'at **)'); + // } + // return str; + // }), ); } else { - expect(errorsArr).toEqual(toBeProdArr); + expect(mappedErrows).toEqual(toBeProdArr); } } @@ -453,7 +466,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); }; @@ -501,12 +514,18 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack(['Lazy', 'Suspense', 'div', 'App']), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); // The client rendered HTML is now in place. @@ -590,7 +609,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); Scheduler.unstable_flushAll(); @@ -615,12 +634,18 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack(['~lazy-element~', 'Suspense', 'div', 'App']), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); // The client rendered HTML is now in place. @@ -681,7 +706,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); Scheduler.unstable_flushAll(); @@ -691,12 +716,18 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack(['Erroring', 'Suspense', 'div', 'App']), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); }); @@ -744,7 +775,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); Scheduler.unstable_flushAll(); @@ -764,12 +795,18 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack(['Lazy', 'Suspense', 'div', 'App']), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); // The client rendered HTML is now in place. @@ -1057,7 +1094,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); Scheduler.unstable_flushAll(); @@ -1761,7 +1798,7 @@ describe('ReactDOMFizzServer', () => { // Attempt to hydrate the content. ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); Scheduler.unstable_flushAll(); @@ -1795,9 +1832,9 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack([ 'AsyncText', 'h1', @@ -1806,8 +1843,14 @@ describe('ReactDOMFizzServer', () => { 'Suspense', 'App', ]), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); // The client rendered HTML is now in place. @@ -3114,8 +3157,6 @@ describe('ReactDOMFizzServer', () => { }); //@gate experimental it('escapes such that attributes cannot be masked', async () => { - window.__outlet = {}; - const dangerousErrorString = '" data-msg="bad message" data-foo="'; const theError = new Error(dangerousErrorString); @@ -3154,7 +3195,7 @@ describe('ReactDOMFizzServer', () => { const errors = []; ReactDOMClient.hydrateRoot(container, , { onRecoverableError(error) { - errors.push(error.message); + errors.push(error); }, }); expect(Scheduler).toFlushAndYield([]); @@ -3163,12 +3204,18 @@ describe('ReactDOMFizzServer', () => { expectErrors( errors, [ - theError.message + - '\nServer Error Hash: ' + - expectedHash + + [ + theError.message, + expectedHash, componentStack(['Erroring', 'Suspense', 'div', 'App']), + ], + ], + [ + [ + 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', + expectedHash, + ], ], - [expectedHash], ); }); }); diff --git a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js index baea9b2367b61..7e489d3e12492 100644 --- a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js @@ -1506,10 +1506,10 @@ const endSuspenseBoundary = stringToPrecomputedChunk(''); const clientRenderedSuspenseBoundaryError1 = stringToPrecomputedChunk( '