[Flight] Emit Deduped Server Components Marker (#31737)
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.
Sebastian Markbåge committed
Dec 16, 2024 at 13:16 UTC
bdf187174d610eb6471bfe39f0d80ab33b68cb3a
3 files changed
+60
-6
fixtures/flight/src/App.js
+3
-2
@@ -27,7 +27,8 @@ function Foo({children}) {
27
return <div>{children}</div>;
28
}
29
30
-function Bar({children}) {
30
+async function Bar({children}) {
31
+ await new Promise(resolve => setTimeout(() => resolve('deferred text'), 10));
32
return <div>{children}</div>;
33
}
34
@@ -81,7 +82,7 @@ export default async function App({prerender}) {
82
<Client />
83
<Note />
84
<Foo>{dedupedChild}</Foo>
84
- <Bar>{dedupedChild}</Bar>
85
+ <Bar>{Promise.resolve([dedupedChild])}</Bar>
86
</Container>
87
</body>
88
</html>
packages/react-client/src/ReactFlightClient.js
+41
-4
@@ -72,6 +72,7 @@ import {readTemporaryReference} from './ReactFlightTemporaryReferences';
72
import {
73
markAllTracksInOrder,
74
logComponentRender,
75
+ logDedupedComponentRender,
76
} from './ReactFlightPerformanceTrack';
77
78
import {
@@ -130,6 +131,7 @@ export type JSONValue =
131
type ProfilingResult = {
132
track: number,
133
endTime: number,
134
+ component: null | ReactComponentInfo,
135
};
136
137
const ROW_ID = 0;
@@ -647,7 +649,7 @@ export function reportGlobalError(response: Response, error: Error): void {
649
});
650
if (enableProfilerTimer && enableComponentPerformanceTrack) {
651
markAllTracksInOrder();
650
- flushComponentPerformance(getChunk(response, 0), 0, -Infinity);
652
+ flushComponentPerformance(getChunk(response, 0), 0, -Infinity, -Infinity);
653
}
654
}
655
@@ -2748,7 +2750,8 @@ function resolveTypedArray(
2750
function flushComponentPerformance(
2751
root: SomeChunk<any>,
2752
trackIdx: number, // Next available track
2751
- trackTime: number, // The time after which it is available
2753
+ trackTime: number, // The time after which it is available,
2754
+ parentEndTime: number,
2755
): ProfilingResult {
2756
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
2757
// eslint-disable-next-line react-internal/prod-error-codes
@@ -2765,6 +2768,22 @@ function flushComponentPerformance(
2768
// chunk in two places. We should extend the current end time as if it was
2769
// rendered as part of this tree.
2770
const previousResult: ProfilingResult = root._children;
2771
+ const previousEndTime = previousResult.endTime;
2772
+ if (
2773
+ parentEndTime > -Infinity &&
2774
+ parentEndTime < previousEndTime &&
2775
+ previousResult.component !== null
2776
+ ) {
2777
+ // Log a placeholder for the deduped value under this child starting
2778
+ // from the end of the self time of the parent and spanning until the
2779
+ // the deduped end.
2780
+ logDedupedComponentRender(
2781
+ previousResult.component,
2782
+ trackIdx,
2783
+ parentEndTime,
2784
+ previousEndTime,
2785
+ );
2786
+ }
2787
// Since we didn't bump the track this time, we just return the same track.
2788
previousResult.track = trackIdx;
2789
return previousResult;
@@ -2792,15 +2811,27 @@ function flushComponentPerformance(
2811
// The start time of this component is before the end time of the previous
2812
// component on this track so we need to bump the next one to a parallel track.
2813
trackIdx++;
2795
- trackTime = startTime;
2814
}
2815
+ trackTime = startTime;
2816
break;
2817
}
2818
}
2819
}
2820
+ for (let i = debugInfo.length - 1; i >= 0; i--) {
2821
+ const info = debugInfo[i];
2822
+ if (typeof info.time === 'number') {
2823
+ if (info.time > parentEndTime) {
2824
+ parentEndTime = info.time;
2825
+ }
2826
+ }
2827
+ }
2828
}
2829
2803
- const result: ProfilingResult = {track: trackIdx, endTime: -Infinity};
2830
+ const result: ProfilingResult = {
2831
+ track: trackIdx,
2832
+ endTime: -Infinity,
2833
+ component: null,
2834
+ };
2835
root._children = result;
2836
let childrenEndTime = -Infinity;
2837
let childTrackIdx = trackIdx;
@@ -2810,7 +2841,11 @@ function flushComponentPerformance(
2841
children[i],
2842
childTrackIdx,
2843
childTrackTime,
2844
+ parentEndTime,
2845
);
2846
+ if (childResult.component !== null) {
2847
+ result.component = childResult.component;
2848
+ }
2849
childTrackIdx = childResult.track;
2850
const childEndTime = childResult.endTime;
2851
childTrackTime = childEndTime;
@@ -2842,6 +2877,8 @@ function flushComponentPerformance(
2877
endTime,
2878
childrenEndTime,
2879
);
2880
+ // Track the root most component of the result for deduping logging.
2881
+ result.component = componentInfo;
2882
}
2883
}
2884
}
packages/react-client/src/ReactFlightPerformanceTrack.js
+16
@@ -90,3 +90,19 @@ export function logComponentRender(
90
performance.measure(name, reusableComponentOptions);
91
}
92
}
93
+
94
+export function logDedupedComponentRender(
95
+ componentInfo: ReactComponentInfo,
96
+ trackIdx: number,
97
+ startTime: number,
98
+ endTime: number,
99
+): void {
100
+ if (supportsUserTiming && endTime >= 0 && trackIdx < 10) {
101
+ const name = componentInfo.name;
102
+ reusableComponentDevToolDetails.color = 'tertiary-light';
103
+ reusableComponentDevToolDetails.track = trackNames[trackIdx];
104
+ reusableComponentOptions.start = startTime < 0 ? 0 : startTime;
105
+ reusableComponentOptions.end = endTime;
106
+ performance.measure(name + ' [deduped]', reusableComponentOptions);
107
+ }
108
+}