@samitouri / QOS-React-2 / commits / 372ec00c03

Update ReactDebugInfo types to declare timing info separately (#31714)

This clarifies a few things by ensuring that there is always at least one required field. This can be used to refine the object to one of the specific types. However, it's probably just a matter of time until we make this tagged unions instead. E.g. it would be nice to rename the `name` field `ReactComponentInfo` to `type` and tag it with the React Element symbol because then it's just the same as a React Element. I also extract a time field. The idea is that this will advance (or rewind) the time to the new timestamp and then anything below would be defined as happening within that time stamp. E.g. to model the start and end for a server component you'd do something like: ``` [ {time: 123}, {name: 'Component', ... }, {time: 124}, ] ``` The reason this needs to be in the `ReactDebugInfo` is so that timing information from one environment gets transferred into the next environment. It lets you take a Promise from one world and transfer it into another world and its timing information is preserved without everything else being preserved. I've gone back and forth on if this should be part of each other Info object like `ReactComponentInfo` but since those can be deduped and can change formats (e.g. this should really just be a React Element) it's better to store this separately. The time format is relative to a `timeOrigin` which is the current environment's `timeOrigin`. When it's serialized between environments this needs to be considered. Emitting these timings is not yet implemented in this PR. --------- Co-authored-by: eps1lon <sebastian.silbermann@vercel.com>

Sebastian Markbåge committed Dec 9, 2024 at 19:47 UTC 372ec00c0384cd2089651154ea7c67693ee3f2a5
3 files changed +59 -18
packages/react-client/src/ReactFlightClient.js
+36 -13
@@ -11,7 +11,9 @@ import type {
11 Thenable,
12 ReactDebugInfo,
13 ReactComponentInfo,
14 + ReactEnvironmentInfo,
15 ReactAsyncInfo,
16 + ReactTimeInfo,
17 ReactStackTrace,
18 ReactCallSite,
19 } from 'shared/ReactTypes';
@@ -2460,7 +2462,6 @@ function initializeFakeStack(
2462 const stack = debugInfo.stack;
2463 const env = debugInfo.env == null ? '' : debugInfo.env;
2464 // $FlowFixMe[cannot-write]
2463 - // $FlowFixMe[prop-missing]
2465 debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env);
2466 }
2467 if (debugInfo.owner != null) {
@@ -2472,7 +2473,11 @@ function initializeFakeStack(
2473 function resolveDebugInfo(
2474 response: Response,
2475 id: number,
2475 - debugInfo: ReactComponentInfo | ReactAsyncInfo,
2476 + debugInfo:
2477 + | ReactComponentInfo
2478 + | ReactEnvironmentInfo
2479 + | ReactAsyncInfo
2480 + | ReactTimeInfo,
2481 ): void {
2482 if (!__DEV__) {
2483 // These errors should never make it into a build so we don't need to encode them in codes.json
@@ -2486,16 +2491,26 @@ function resolveDebugInfo(
2491 // to initialize it when we need it, we might be inside user code.
2492 const env =
2493 debugInfo.env === undefined ? response._rootEnvironmentName : debugInfo.env;
2489 - initializeFakeTask(response, debugInfo, env);
2494 + if (debugInfo.stack !== undefined) {
2495 + const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
2496 + // $FlowFixMe[incompatible-type]
2497 + debugInfo;
2498 + initializeFakeTask(response, componentInfoOrAsyncInfo, env);
2499 + }
2500 if (debugInfo.owner === null && response._debugRootOwner != null) {
2491 - // $FlowFixMe
2492 - debugInfo.owner = response._debugRootOwner;
2501 + // $FlowFixMe[prop-missing] By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
2502 + const componentInfo: ReactComponentInfo = debugInfo;
2503 + // $FlowFixMe[cannot-write]
2504 + componentInfo.owner = response._debugRootOwner;
2505 // We override the stack if we override the owner since the stack where the root JSX
2506 // was created on the server isn't very useful but where the request was made is.
2495 - // $FlowFixMe
2496 - debugInfo.debugStack = response._debugRootStack;
2497 - } else {
2498 - initializeFakeStack(response, debugInfo);
2507 + // $FlowFixMe[cannot-write]
2508 + componentInfo.debugStack = response._debugRootStack;
2509 + } else if (debugInfo.stack !== undefined) {
2510 + const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
2511 + // $FlowFixMe[incompatible-type]
2512 + debugInfo;
2513 + initializeFakeStack(response, componentInfoOrAsyncInfo);
2514 }
2515
2516 const chunk = getChunk(response, id);
@@ -2779,11 +2794,19 @@ function processFullStringRow(
2794 }
2795 case 68 /* "D" */: {
2796 if (__DEV__) {
2782 - const chunk: ResolvedModelChunk<ReactComponentInfo | ReactAsyncInfo> =
2783 - createResolvedModelChunk(response, row);
2797 + const chunk: ResolvedModelChunk<
2798 + | ReactComponentInfo
2799 + | ReactEnvironmentInfo
2800 + | ReactAsyncInfo
2801 + | ReactTimeInfo,
2802 + > = createResolvedModelChunk(response, row);
2803 initializeModelChunk(chunk);
2785 - const initializedChunk: SomeChunk<ReactComponentInfo | ReactAsyncInfo> =
2786 - chunk;
2804 + const initializedChunk: SomeChunk<
2805 + | ReactComponentInfo
2806 + | ReactEnvironmentInfo
2807 + | ReactAsyncInfo
2808 + | ReactTimeInfo,
2809 + > = chunk;
2810 if (initializedChunk.status === INITIALIZED) {
2811 resolveDebugInfo(response, id, initializedChunk.value);
2812 } else {
packages/react-server/src/ReactFlightServer.js
+7 -1
@@ -61,7 +61,9 @@ import type {
61 RejectedThenable,
62 ReactDebugInfo,
63 ReactComponentInfo,
64 + ReactEnvironmentInfo,
65 ReactAsyncInfo,
66 + ReactTimeInfo,
67 ReactStackTrace,
68 ReactCallSite,
69 } from 'shared/ReactTypes';
@@ -3267,7 +3269,11 @@ function emitModelChunk(request: Request, id: number, json: string): void {
3269 function emitDebugChunk(
3270 request: Request,
3271 id: number,
3270 - debugInfo: ReactComponentInfo | ReactAsyncInfo,
3272 + debugInfo:
3273 + | ReactComponentInfo
3274 + | ReactAsyncInfo
3275 + | ReactEnvironmentInfo
3276 + | ReactTimeInfo,
3277 ): void {
3278 if (!__DEV__) {
3279 // These errors should never make it into a build so we don't need to encode them in codes.json
packages/shared/ReactTypes.js
+16 -4
@@ -188,7 +188,7 @@ export type ReactCallSite = [
188 export type ReactStackTrace = Array<ReactCallSite>;
189
190 export type ReactComponentInfo = {
191 - +name?: string,
191 + +name: string,
192 +env?: string,
193 +key?: null | string,
194 +owner?: null | ReactComponentInfo,
@@ -199,10 +199,22 @@ export type ReactComponentInfo = {
199 +debugTask?: null | ConsoleTask,
200 };
201
202 +export type ReactEnvironmentInfo = {
203 + +env: string,
204 +};
205 +
206 export type ReactAsyncInfo = {
203 - +started?: number,
204 - +completed?: number,
207 + +type: string,
208 + // Stashed Data for the Specific Execution Environment. Not part of the transport protocol
209 + +debugStack?: null | Error,
210 + +debugTask?: null | ConsoleTask,
211 +stack?: null | ReactStackTrace,
212 };
213
208 -export type ReactDebugInfo = Array<ReactComponentInfo | ReactAsyncInfo>;
214 +export type ReactTimeInfo = {
215 + +time: number, // performance.now
216 +};
217 +
218 +export type ReactDebugInfo = Array<
219 + ReactComponentInfo | ReactEnvironmentInfo | ReactAsyncInfo | ReactTimeInfo,
220 +>;