From 8ba9d5eafb3336a3b6b68d7555e7901845e28d08 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, 167 insertions(+), 168 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( '