Repository: facebook/react
Pull request: [Fizz] Unify preamble only fields to save a field

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.

Changed files:
filename: packages/react-server/src/ReactFizzServer.js
patch: @@ -255,8 +255,7 @@ type SuspenseBoundary = {
   fallbackAbortableTasks: Set<Task>, // 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<Task>, // 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<ComponentStackNode>,
 };
 
-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<Task>,
-  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<Task>,
   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,