Skip to content

Commit 567b90d

Browse files
committed
Include regular stack trace in serialized errors from Fizz
We previously only included the component stack. Also cleaned up the fields in Fizz server that wasn't using consistent hidden classes in dev vs prod.
1 parent cc56bed commit 567b90d

File tree

8 files changed

+126
-38
lines changed

8 files changed

+126
-38
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,22 +1226,29 @@ export function isSuspenseInstanceFallback(
12261226

12271227
export function getSuspenseInstanceFallbackErrorDetails(
12281228
instance: SuspenseInstance,
1229-
): {digest: ?string, message?: string, stack?: string} {
1229+
): {
1230+
digest: ?string,
1231+
message?: string,
1232+
stack?: string,
1233+
componentStack?: string,
1234+
} {
12301235
const dataset =
12311236
instance.nextSibling && ((instance.nextSibling: any): HTMLElement).dataset;
1232-
let digest, message, stack;
1237+
let digest, message, stack, componentStack;
12331238
if (dataset) {
12341239
digest = dataset.dgst;
12351240
if (__DEV__) {
12361241
message = dataset.msg;
12371242
stack = dataset.stck;
1243+
componentStack = dataset.cstck;
12381244
}
12391245
}
12401246
if (__DEV__) {
12411247
return {
12421248
message,
12431249
digest,
12441250
stack,
1251+
componentStack,
12451252
};
12461253
} else {
12471254
// Object gets DCE'd if constructed in tail position and matches callsite destructuring

packages/react-dom-bindings/src/server/ReactDOMServerExternalRuntime.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ function handleNode(node_: Node) {
9494
dataset['dgst'],
9595
dataset['msg'],
9696
dataset['stck'],
97+
dataset['cstck'],
9798
);
9899
node.remove();
99100
} else if (dataset['rri'] != null) {

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3809,6 +3809,8 @@ const clientRenderedSuspenseBoundaryError1B =
38093809
stringToPrecomputedChunk(' data-msg="');
38103810
const clientRenderedSuspenseBoundaryError1C =
38113811
stringToPrecomputedChunk(' data-stck="');
3812+
const clientRenderedSuspenseBoundaryError1D =
3813+
stringToPrecomputedChunk(' data-cstck="');
38123814
const clientRenderedSuspenseBoundaryError2 =
38133815
stringToPrecomputedChunk('></template>');
38143816

@@ -3851,7 +3853,8 @@ export function writeStartClientRenderedSuspenseBoundary(
38513853
destination: Destination,
38523854
renderState: RenderState,
38533855
errorDigest: ?string,
3854-
errorMesssage: ?string,
3856+
errorMessage: ?string,
3857+
errorStack: ?string,
38553858
errorComponentStack: ?string,
38563859
): boolean {
38573860
let result;
@@ -3869,19 +3872,27 @@ export function writeStartClientRenderedSuspenseBoundary(
38693872
);
38703873
}
38713874
if (__DEV__) {
3872-
if (errorMesssage) {
3875+
if (errorMessage) {
38733876
writeChunk(destination, clientRenderedSuspenseBoundaryError1B);
38743877
writeChunk(
38753878
destination,
3876-
stringToChunk(escapeTextForBrowser(errorMesssage)),
3879+
stringToChunk(escapeTextForBrowser(errorMessage)),
38773880
);
38783881
writeChunk(
38793882
destination,
38803883
clientRenderedSuspenseBoundaryErrorAttrInterstitial,
38813884
);
38823885
}
3883-
if (errorComponentStack) {
3886+
if (errorStack) {
38843887
writeChunk(destination, clientRenderedSuspenseBoundaryError1C);
3888+
writeChunk(destination, stringToChunk(escapeTextForBrowser(errorStack)));
3889+
writeChunk(
3890+
destination,
3891+
clientRenderedSuspenseBoundaryErrorAttrInterstitial,
3892+
);
3893+
}
3894+
if (errorComponentStack) {
3895+
writeChunk(destination, clientRenderedSuspenseBoundaryError1D);
38853896
writeChunk(
38863897
destination,
38873898
stringToChunk(escapeTextForBrowser(errorComponentStack)),
@@ -4244,6 +4255,7 @@ const clientRenderData1 = stringToPrecomputedChunk(
42444255
const clientRenderData2 = stringToPrecomputedChunk('" data-dgst="');
42454256
const clientRenderData3 = stringToPrecomputedChunk('" data-msg="');
42464257
const clientRenderData4 = stringToPrecomputedChunk('" data-stck="');
4258+
const clientRenderData5 = stringToPrecomputedChunk('" data-cstck="');
42474259
const clientRenderDataEnd = dataElementQuotedEnd;
42484260

42494261
export function writeClientRenderBoundaryInstruction(
@@ -4252,8 +4264,9 @@ export function writeClientRenderBoundaryInstruction(
42524264
renderState: RenderState,
42534265
id: number,
42544266
errorDigest: ?string,
4255-
errorMessage?: string,
4256-
errorComponentStack?: string,
4267+
errorMessage: ?string,
4268+
errorStack: ?string,
4269+
errorComponentStack: ?string,
42574270
): boolean {
42584271
const scriptFormat =
42594272
!enableFizzExternalRuntime ||
@@ -4284,7 +4297,7 @@ export function writeClientRenderBoundaryInstruction(
42844297
writeChunk(destination, clientRenderScript1A);
42854298
}
42864299

4287-
if (errorDigest || errorMessage || errorComponentStack) {
4300+
if (errorDigest || errorMessage || errorStack || errorComponentStack) {
42884301
if (scriptFormat) {
42894302
// ,"JSONString"
42904303
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
@@ -4301,7 +4314,7 @@ export function writeClientRenderBoundaryInstruction(
43014314
);
43024315
}
43034316
}
4304-
if (errorMessage || errorComponentStack) {
4317+
if (errorMessage || errorStack || errorComponentStack) {
43054318
if (scriptFormat) {
43064319
// ,"JSONString"
43074320
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
@@ -4318,6 +4331,23 @@ export function writeClientRenderBoundaryInstruction(
43184331
);
43194332
}
43204333
}
4334+
if (errorStack || errorComponentStack) {
4335+
// ,"JSONString"
4336+
if (scriptFormat) {
4337+
writeChunk(destination, clientRenderErrorScriptArgInterstitial);
4338+
writeChunk(
4339+
destination,
4340+
stringToChunk(escapeJSStringsForInstructionScripts(errorStack || '')),
4341+
);
4342+
} else {
4343+
// " data-stck="HTMLString
4344+
writeChunk(destination, clientRenderData4);
4345+
writeChunk(
4346+
destination,
4347+
stringToChunk(escapeTextForBrowser(errorStack || '')),
4348+
);
4349+
}
4350+
}
43214351
if (errorComponentStack) {
43224352
// ,"JSONString"
43234353
if (scriptFormat) {
@@ -4329,8 +4359,8 @@ export function writeClientRenderBoundaryInstruction(
43294359
),
43304360
);
43314361
} else {
4332-
// " data-stck="HTMLString
4333-
writeChunk(destination, clientRenderData4);
4362+
// " data-cstck="HTMLString
4363+
writeChunk(destination, clientRenderData5);
43344364
writeChunk(
43354365
destination,
43364366
stringToChunk(escapeTextForBrowser(errorComponentStack)),

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export function writeStartClientRenderedSuspenseBoundary(
219219
// flushing these error arguments are not currently supported in this legacy streaming format.
220220
errorDigest: ?string,
221221
errorMessage: ?string,
222+
errorStack: ?string,
222223
errorComponentStack: ?string,
223224
): boolean {
224225
if (renderState.generateStaticMarkup) {
@@ -231,6 +232,7 @@ export function writeStartClientRenderedSuspenseBoundary(
231232
renderState,
232233
errorDigest,
233234
errorMessage,
235+
errorStack,
234236
errorComponentStack,
235237
);
236238
}

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function clientRenderBoundary(
1919
suspenseBoundaryID,
2020
errorDigest,
2121
errorMsg,
22+
errorStack,
2223
errorComponentStack,
2324
) {
2425
// Find the fallback's first element.
@@ -36,7 +37,8 @@ export function clientRenderBoundary(
3637
const dataset = suspenseIdNode.dataset;
3738
if (errorDigest) dataset['dgst'] = errorDigest;
3839
if (errorMsg) dataset['msg'] = errorMsg;
39-
if (errorComponentStack) dataset['stck'] = errorComponentStack;
40+
if (errorStack) dataset['stck'] = errorStack;
41+
if (errorComponentStack) dataset['cstck'] = errorComponentStack;
4042
// Tell React to retry it if the parent already hydrated.
4143
if (suspenseNode['_reactRetry']) {
4244
suspenseNode['_reactRetry']();

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,9 +2647,9 @@ function updateDehydratedSuspenseComponent(
26472647
// get an update and we'll never be able to hydrate the final content. Let's just try the
26482648
// client side render instead.
26492649
let digest: ?string;
2650-
let message, stack;
2650+
let message, stack, componentStack;
26512651
if (__DEV__) {
2652-
({digest, message, stack} =
2652+
({digest, message, stack, componentStack} =
26532653
getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
26542654
} else {
26552655
({digest} = getSuspenseInstanceFallbackErrorDetails(suspenseInstance));
@@ -2669,8 +2669,14 @@ function updateDehydratedSuspenseComponent(
26692669
'client rendering.',
26702670
);
26712671
}
2672+
// Replace the stack with the server stack
2673+
error.stack = stack || '';
26722674
(error: any).digest = digest;
2673-
capturedValue = createCapturedValueFromError(error, digest, stack);
2675+
capturedValue = createCapturedValueFromError(
2676+
error,
2677+
digest,
2678+
componentStack,
2679+
);
26742680
}
26752681
return retrySuspenseComponentWithoutHydrating(
26762682
current,

packages/react-server/src/ReactFizzServer.js

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,6 @@ const CLIENT_RENDERED = 4; // if it errors or infinitely suspends
203203
type SuspenseBoundary = {
204204
status: 0 | 1 | 4 | 5,
205205
rootSegmentID: number,
206-
errorDigest: ?string, // the error hash if it errors
207-
errorMessage?: string, // the error string if it errors
208-
errorComponentStack?: string, // the error component stack if it errors
209206
parentFlushed: boolean,
210207
pendingTasks: number, // when it reaches zero we can show this boundary's content
211208
completedSegments: Array<Segment>, // completed but not yet flushed segments.
@@ -215,6 +212,11 @@ type SuspenseBoundary = {
215212
fallbackState: HoistableState,
216213
trackedContentKeyPath: null | KeyNode, // used to track the path for replay nodes
217214
trackedFallbackNode: null | ReplayNode, // used to track the fallback for replay nodes
215+
errorDigest: ?string, // the error hash if it errors
216+
// DEV-only fields
217+
errorMessage?: null | string, // the error string if it errors
218+
errorStack?: null | string, // the error stack if it errors
219+
errorComponentStack?: null | string, // the error component stack if it errors
218220
};
219221

220222
type RenderTask = {
@@ -601,7 +603,7 @@ function createSuspenseBoundary(
601603
request: Request,
602604
fallbackAbortableTasks: Set<Task>,
603605
): SuspenseBoundary {
604-
return {
606+
const boundary: SuspenseBoundary = {
605607
status: PENDING,
606608
rootSegmentID: -1,
607609
parentFlushed: false,
@@ -615,6 +617,13 @@ function createSuspenseBoundary(
615617
trackedContentKeyPath: null,
616618
trackedFallbackNode: null,
617619
};
620+
if (__DEV__) {
621+
// DEV-only fields for hidden class
622+
boundary.errorMessage = null;
623+
boundary.errorStack = null;
624+
boundary.errorComponentStack = null;
625+
}
626+
return boundary;
618627
}
619628

620629
function createRenderTask(
@@ -814,19 +823,24 @@ function encodeErrorForBoundary(
814823
) {
815824
boundary.errorDigest = digest;
816825
if (__DEV__) {
817-
let message;
826+
let message, stack;
818827
// In dev we additionally encode the error message and component stack on the boundary
819828
if (error instanceof Error) {
820829
// eslint-disable-next-line react-internal/safe-string-coercion
821830
message = String(error.message);
831+
// eslint-disable-next-line react-internal/safe-string-coercion
832+
stack = String(error.stack);
822833
} else if (typeof error === 'object' && error !== null) {
823834
message = describeObjectForErrorMessage(error);
835+
stack = null;
824836
} else {
825837
// eslint-disable-next-line react-internal/safe-string-coercion
826838
message = String(error);
839+
stack = null;
827840
}
828841

829842
boundary.errorMessage = message;
843+
boundary.errorStack = stack;
830844
boundary.errorComponentStack = thrownInfo.componentStack;
831845
}
832846
}
@@ -3727,13 +3741,25 @@ function flushSegment(
37273741
// Emit a client rendered suspense boundary wrapper.
37283742
// We never queue the inner boundary so we'll never emit its content or partial segments.
37293743

3730-
writeStartClientRenderedSuspenseBoundary(
3731-
destination,
3732-
request.renderState,
3733-
boundary.errorDigest,
3734-
boundary.errorMessage,
3735-
boundary.errorComponentStack,
3736-
);
3744+
if (__DEV__) {
3745+
writeStartClientRenderedSuspenseBoundary(
3746+
destination,
3747+
request.renderState,
3748+
boundary.errorDigest,
3749+
boundary.errorMessage,
3750+
boundary.errorStack,
3751+
boundary.errorComponentStack,
3752+
);
3753+
} else {
3754+
writeStartClientRenderedSuspenseBoundary(
3755+
destination,
3756+
request.renderState,
3757+
boundary.errorDigest,
3758+
null,
3759+
null,
3760+
null,
3761+
);
3762+
}
37373763
// Flush the fallback.
37383764
flushSubtree(request, destination, segment, hoistableState);
37393765

@@ -3819,15 +3845,29 @@ function flushClientRenderedBoundary(
38193845
destination: Destination,
38203846
boundary: SuspenseBoundary,
38213847
): boolean {
3822-
return writeClientRenderBoundaryInstruction(
3823-
destination,
3824-
request.resumableState,
3825-
request.renderState,
3826-
boundary.rootSegmentID,
3827-
boundary.errorDigest,
3828-
boundary.errorMessage,
3829-
boundary.errorComponentStack,
3830-
);
3848+
if (__DEV__) {
3849+
return writeClientRenderBoundaryInstruction(
3850+
destination,
3851+
request.resumableState,
3852+
request.renderState,
3853+
boundary.rootSegmentID,
3854+
boundary.errorDigest,
3855+
boundary.errorMessage,
3856+
boundary.errorStack,
3857+
boundary.errorComponentStack,
3858+
);
3859+
} else {
3860+
return writeClientRenderBoundaryInstruction(
3861+
destination,
3862+
request.resumableState,
3863+
request.renderState,
3864+
boundary.rootSegmentID,
3865+
boundary.errorDigest,
3866+
null,
3867+
null,
3868+
null,
3869+
);
3870+
}
38313871
}
38323872

38333873
function flushSegmentContainer(

0 commit comments

Comments
 (0)