From 476f6d04aba77aefa1de2c5734e3afe7c2fcb9bc Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 28 Jun 2024 21:36:26 -0400 Subject: [PATCH] Include a component stack in prod but only lazily generate it --- packages/react-server/src/ReactFizzServer.js | 54 +++++++++----------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 6c21a1828da58..531b6c4d67744 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -904,27 +904,23 @@ type ThrownInfo = { export type ErrorInfo = ThrownInfo; export type PostponeInfo = ThrownInfo; -// While we track component stacks in prod all the time we only produce a reified stack in dev and -// during prerender in Prod. The reason for this is that the stack is useful for prerender where the timeliness -// of the request is less critical than the observability of the execution. For renders and resumes however we -// prioritize speed of the request. -function getThrownInfo( - request: Request, - node: null | ComponentStackNode, -): ThrownInfo { - if ( - node && - // Always produce a stack in dev - (__DEV__ || - // Produce a stack in prod if we're in a prerender - request.trackedPostpones !== null) - ) { - return { - componentStack: getStackFromNode(node), - }; - } else { - return {}; +function getThrownInfo(node: null | ComponentStackNode): ThrownInfo { + const errorInfo: ThrownInfo = {}; + if (node) { + Object.defineProperty(errorInfo, 'componentStack', { + configurable: true, + enumerable: true, + get() { + // Lazyily generate the stack since it's expensive. + const stack = getStackFromNode(node); + Object.defineProperty(errorInfo, 'componentStack', { + value: stack, + }); + return stack; + }, + }); } + return errorInfo; } function encodeErrorForBoundary( @@ -1123,7 +1119,7 @@ function renderSuspenseBoundary( } catch (error: mixed) { contentRootSegment.status = ERRORED; newBoundary.status = CLIENT_RENDERED; - const thrownInfo = getThrownInfo(request, task.componentStack); + const thrownInfo = getThrownInfo(task.componentStack); let errorDigest; if ( enablePostpone && @@ -1269,7 +1265,7 @@ function replaySuspenseBoundary( } } catch (error: mixed) { resumedBoundary.status = CLIENT_RENDERED; - const thrownInfo = getThrownInfo(request, task.componentStack); + const thrownInfo = getThrownInfo(task.componentStack); let errorDigest; if ( enablePostpone && @@ -2333,7 +2329,7 @@ function replayElement( // in the original prerender. What's unable to complete is the child // replay nodes which might be Suspense boundaries which are able to // absorb the error and we can still continue with siblings. - const thrownInfo = getThrownInfo(request, task.componentStack); + const thrownInfo = getThrownInfo(task.componentStack); erroredReplay( request, task.blockedBoundary, @@ -2864,7 +2860,7 @@ function replayFragment( // replay nodes which might be Suspense boundaries which are able to // absorb the error and we can still continue with siblings. // This is an error, stash the component stack if it is null. - const thrownInfo = getThrownInfo(request, task.componentStack); + const thrownInfo = getThrownInfo(task.componentStack); erroredReplay( request, task.blockedBoundary, @@ -3457,7 +3453,7 @@ function renderNode( const trackedPostpones = request.trackedPostpones; const postponeInstance: Postpone = (x: any); - const thrownInfo = getThrownInfo(request, task.componentStack); + const thrownInfo = getThrownInfo(task.componentStack); const postponedSegment = injectPostponedHole( request, ((task: any): RenderTask), // We don't use ReplayTasks in prerenders. @@ -3768,7 +3764,7 @@ function abortTask(task: Task, request: Request, error: mixed): void { boundary.status = CLIENT_RENDERED; // We construct an errorInfo from the boundary's componentStack so the error in dev will indicate which // boundary the message is referring to - const errorInfo = getThrownInfo(request, task.componentStack); + const errorInfo = getThrownInfo(task.componentStack); let errorDigest; if ( enablePostpone && @@ -4060,7 +4056,7 @@ function retryRenderTask( task.abortSet.delete(task); const postponeInstance: Postpone = (x: any); - const postponeInfo = getThrownInfo(request, task.componentStack); + const postponeInfo = getThrownInfo(task.componentStack); logPostpone(request, postponeInstance.message, postponeInfo); trackPostpone(request, trackedPostpones, task, segment); finishedTask(request, task.blockedBoundary, segment); @@ -4068,7 +4064,7 @@ function retryRenderTask( } } - const errorInfo = getThrownInfo(request, task.componentStack); + const errorInfo = getThrownInfo(task.componentStack); task.abortSet.delete(task); segment.status = ERRORED; erroredTask(request, task.blockedBoundary, x, errorInfo); @@ -4142,7 +4138,7 @@ function retryReplayTask(request: Request, task: ReplayTask): void { } task.replay.pendingTasks--; task.abortSet.delete(task); - const errorInfo = getThrownInfo(request, task.componentStack); + const errorInfo = getThrownInfo(task.componentStack); erroredReplay( request, task.blockedBoundary,