@samitouri / QOS-React / commits / bdf187174d

[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 +}