From fa50caf5f84bb8976aa1dbff7f15a821e44e7af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 7 Nov 2025 09:19:19 -0500 Subject: [PATCH] [Fizz] Unify preamble only fields to save a field (#35068) Stacked on #35067. Same idea of saving a field on the SuspenseBoundary in the common case. The case where they can have a preamble is rare. --- packages/react-server/src/ReactFizzServer.js | 60 ++++++++++---------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 7d4db20d86..3a92a17377 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -255,8 +255,7 @@ type SuspenseBoundary = { fallbackAbortableTasks: Set, // used to cancel task on the fallback if the boundary completes or gets canceled. contentState: HoistableState, fallbackState: HoistableState, - contentPreamble: null | Preamble, - fallbackPreamble: null | Preamble, + preamble: null | Preamble, tracked: null | { contentKeyPath: null | KeyNode, // used to track the path for replay nodes fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes @@ -275,7 +274,7 @@ type RenderTask = { ping: () => void, blockedBoundary: Root | SuspenseBoundary, blockedSegment: Segment, // the segment we'll write to - blockedPreamble: null | Preamble, + blockedPreamble: null | PreambleState, hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary abortSet: Set, // the abortable set that this task belongs to keyPath: Root | KeyNode, // the path of all parent keys currently rendering @@ -398,7 +397,17 @@ export opaque type Request = { didWarnForKey?: null | WeakSet, }; -type Preamble = PreambleState; +type Preamble = { + content: PreambleState, + fallback: PreambleState, +}; + +function createPreamble(): Preamble { + return { + content: createPreambleState(), + fallback: createPreambleState(), + }; +} // This is a default heuristic for how to split up the HTML content into progressive // loading. Our goal is to be able to display additional new content about every 500ms. @@ -466,7 +475,7 @@ function isEligibleForOutlining( // For boundaries that can possibly contribute to the preamble we don't want to outline // them regardless of their size since the fallbacks should only be emitted if we've // errored the boundary. - boundary.contentPreamble === null + boundary.preamble === null ); } @@ -786,8 +795,7 @@ function createSuspenseBoundary( request: Request, row: null | SuspenseListRow, fallbackAbortableTasks: Set, - contentPreamble: null | Preamble, - fallbackPreamble: null | Preamble, + preamble: null | Preamble, defer: boolean, ): SuspenseBoundary { const boundary: SuspenseBoundary = { @@ -803,8 +811,7 @@ function createSuspenseBoundary( errorDigest: null, contentState: createHoistableState(), fallbackState: createHoistableState(), - contentPreamble, - fallbackPreamble, + preamble, tracked: null, }; if (__DEV__) { @@ -838,7 +845,7 @@ function createRenderTask( childIndex: number, blockedBoundary: Root | SuspenseBoundary, blockedSegment: Segment, - blockedPreamble: null | Preamble, + blockedPreamble: null | PreambleState, hoistableState: null | HoistableState, abortSet: Set, keyPath: Root | KeyNode, @@ -1290,8 +1297,7 @@ function renderSuspenseBoundary( request, task.row, fallbackAbortSet, - createPreambleState(), - createPreambleState(), + createPreamble(), defer, ); } else { @@ -1300,7 +1306,6 @@ function renderSuspenseBoundary( task.row, fallbackAbortSet, null, - null, defer, ); } @@ -1365,7 +1370,8 @@ function renderSuspenseBoundary( } task.blockedSegment = boundarySegment; - task.blockedPreamble = newBoundary.fallbackPreamble; + task.blockedPreamble = + newBoundary.preamble === null ? null : newBoundary.preamble.fallback; task.keyPath = fallbackKeyPath; task.formatContext = getSuspenseFallbackFormatContext( request.resumableState, @@ -1409,7 +1415,7 @@ function renderSuspenseBoundary( -1, newBoundary, contentRootSegment, - newBoundary.contentPreamble, + newBoundary.preamble === null ? null : newBoundary.preamble.content, newBoundary.contentState, task.abortSet, keyPath, @@ -1440,7 +1446,8 @@ function renderSuspenseBoundary( // context switching. We just need to temporarily switch which boundary and which segment // we're writing to. If something suspends, it'll spawn new suspended task with that context. task.blockedBoundary = newBoundary; - task.blockedPreamble = newBoundary.contentPreamble; + task.blockedPreamble = + newBoundary.preamble === null ? null : newBoundary.preamble.content; task.hoistableState = newBoundary.contentState; task.blockedSegment = contentRootSegment; task.keyPath = keyPath; @@ -1548,7 +1555,7 @@ function renderSuspenseBoundary( -1, parentBoundary, boundarySegment, - newBoundary.fallbackPreamble, + newBoundary.preamble === null ? null : newBoundary.preamble.fallback, newBoundary.fallbackState, fallbackAbortSet, fallbackKeyPath, @@ -1602,8 +1609,7 @@ function replaySuspenseBoundary( request, task.row, fallbackAbortSet, - createPreambleState(), - createPreambleState(), + createPreamble(), defer, ); } else { @@ -1612,7 +1618,6 @@ function replaySuspenseBoundary( task.row, fallbackAbortSet, null, - null, defer, ); } @@ -4372,7 +4377,7 @@ function erroredTask( if ( request.pendingRootTasks === 0 && request.trackedPostpones === null && - boundary.contentPreamble !== null + boundary.preamble !== null ) { // The root is complete and this boundary may contribute part of the preamble. // We eagerly attempt to prepare the preamble here because we expect most requests @@ -4414,7 +4419,6 @@ function abortRemainingSuspenseBoundary( null, new Set(), null, - null, false, ); resumedBoundary.parentFlushed = true; @@ -4894,7 +4898,7 @@ function finishedTask( if ( request.pendingRootTasks === 0 && request.trackedPostpones === null && - boundary.contentPreamble !== null + boundary.preamble !== null ) { // The root is complete and this boundary may contribute part of the preamble. // We eagerly attempt to prepare the preamble here because we expect most requests @@ -5271,10 +5275,8 @@ function preparePreambleFromSegment( ); } - const preamble = boundary.contentPreamble; - const fallbackPreamble = boundary.fallbackPreamble; - - if (preamble === null || fallbackPreamble === null) { + const preamble = boundary.preamble; + if (preamble === null) { // This boundary cannot have a preamble so it can't block the flushing of // the preamble. return false; @@ -5286,7 +5288,7 @@ function preparePreambleFromSegment( case COMPLETED: { // This boundary is complete. It might have inner boundaries which are pending // and able to provide a preamble so we have to check it's children - hoistPreambleState(request.renderState, preamble); + hoistPreambleState(request.renderState, preamble.content); // We track this boundary's byteSize on the request since it will always flush with // the request since it may contribute to the preamble request.byteSize += boundary.byteSize; @@ -5315,7 +5317,7 @@ function preparePreambleFromSegment( case CLIENT_RENDERED: { if (segment.status === COMPLETED) { // This boundary is errored so if it contains a preamble we should include it - hoistPreambleState(request.renderState, fallbackPreamble); + hoistPreambleState(request.renderState, preamble.fallback); return preparePreambleFromSubtree( request, segment,