Repository: facebook/react
Pull request: [Flight] Emit Deduped Server Components Marker

Stacked on #31736.

<img width="1223" alt="Screenshot 2024-12-11 at 8 21 12 PM" src="https://github.com/user-attachments/assets/a7cbc04b-c831-476b-aa2f-baddec9461c9" />

This emits a placeholder when we're deduping a component. This starts when the parent's self time ends, where we would've started rendering this component if it wasn't already started. The end time is when the actual render ends since the parent is also blocked by it.

Changed files:
filename: fixtures/flight/src/App.js
patch: @@ -27,7 +27,8 @@ function Foo({children}) {
   return <div>{children}</div>;
 }
 
-function Bar({children}) {
+async function Bar({children}) {
+  await new Promise(resolve => setTimeout(() => resolve('deferred text'), 10));
   return <div>{children}</div>;
 }
 
@@ -81,7 +82,7 @@ export default async function App({prerender}) {
           <Client />
           <Note />
           <Foo>{dedupedChild}</Foo>
-          <Bar>{dedupedChild}</Bar>
+          <Bar>{Promise.resolve([dedupedChild])}</Bar>
         </Container>
       </body>
     </html>

filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -72,6 +72,7 @@ import {readTemporaryReference} from './ReactFlightTemporaryReferences';
 import {
   markAllTracksInOrder,
   logComponentRender,
+  logDedupedComponentRender,
 } from './ReactFlightPerformanceTrack';
 
 import {
@@ -130,6 +131,7 @@ export type JSONValue =
 type ProfilingResult = {
   track: number,
   endTime: number,
+  component: null | ReactComponentInfo,
 };
 
 const ROW_ID = 0;
@@ -647,7 +649,7 @@ export function reportGlobalError(response: Response, error: Error): void {
   });
   if (enableProfilerTimer && enableComponentPerformanceTrack) {
     markAllTracksInOrder();
-    flushComponentPerformance(getChunk(response, 0), 0, -Infinity);
+    flushComponentPerformance(getChunk(response, 0), 0, -Infinity, -Infinity);
   }
 }
 
@@ -2748,7 +2750,8 @@ function resolveTypedArray(
 function flushComponentPerformance(
   root: SomeChunk<any>,
   trackIdx: number, // Next available track
-  trackTime: number, // The time after which it is available
+  trackTime: number, // The time after which it is available,
+  parentEndTime: number,
 ): ProfilingResult {
   if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
     // eslint-disable-next-line react-internal/prod-error-codes
@@ -2765,6 +2768,22 @@ function flushComponentPerformance(
     // chunk in two places. We should extend the current end time as if it was
     // rendered as part of this tree.
     const previousResult: ProfilingResult = root._children;
+    const previousEndTime = previousResult.endTime;
+    if (
+      parentEndTime > -Infinity &&
+      parentEndTime < previousEndTime &&
+      previousResult.component !== null
+    ) {
+      // Log a placeholder for the deduped value under this child starting
+      // from the end of the self time of the parent and spanning until the
+      // the deduped end.
+      logDedupedComponentRender(
+        previousResult.component,
+        trackIdx,
+        parentEndTime,
+        previousEndTime,
+      );
+    }
     // Since we didn't bump the track this time, we just return the same track.
     previousResult.track = trackIdx;
     return previousResult;
@@ -2792,15 +2811,27 @@ function flushComponentPerformance(
             // The start time of this component is before the end time of the previous
             // component on this track so we need to bump the next one to a parallel track.
             trackIdx++;
-            trackTime = startTime;
           }
+          trackTime = startTime;
           break;
         }
       }
     }
+    for (let i = debugInfo.length - 1; i >= 0; i--) {
+      const info = debugInfo[i];
+      if (typeof info.time === 'number') {
+        if (info.time > parentEndTime) {
+          parentEndTime = info.time;
+        }
+      }
+    }
   }
 
-  const result: ProfilingResult = {track: trackIdx, endTime: -Infinity};
+  const result: ProfilingResult = {
+    track: trackIdx,
+    endTime: -Infinity,
+    component: null,
+  };
   root._children = result;
   let childrenEndTime = -Infinity;
   let childTrackIdx = trackIdx;
@@ -2810,7 +2841,11 @@ function flushComponentPerformance(
       children[i],
       childTrackIdx,
       childTrackTime,
+      parentEndTime,
     );
+    if (childResult.component !== null) {
+      result.component = childResult.component;
+    }
     childTrackIdx = childResult.track;
     const childEndTime = childResult.endTime;
     childTrackTime = childEndTime;
@@ -2842,6 +2877,8 @@ function flushComponentPerformance(
             endTime,
             childrenEndTime,
           );
+          // Track the root most component of the result for deduping logging.
+          result.component = componentInfo;
         }
       }
     }

filename: packages/react-client/src/ReactFlightPerformanceTrack.js
patch: @@ -90,3 +90,19 @@ export function logComponentRender(
     performance.measure(name, reusableComponentOptions);
   }
 }
+
+export function logDedupedComponentRender(
+  componentInfo: ReactComponentInfo,
+  trackIdx: number,
+  startTime: number,
+  endTime: number,
+): void {
+  if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
+    const name = componentInfo.name;
+    reusableComponentDevToolDetails.color = 'tertiary-light';
+    reusableComponentDevToolDetails.track = trackNames[trackIdx];
+    reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
+    reusableComponentOptions.end = endTime;
+    performance.measure(name + ' [deduped]', reusableComponentOptions);
+  }
+}