Repository: facebook/react
Pull request: Update ReactDebugInfo types to declare timing info separately

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.

Changed files:
filename: packages/react-client/src/ReactFlightClient.js
patch: @@ -11,7 +11,9 @@ import type {
   Thenable,
   ReactDebugInfo,
   ReactComponentInfo,
+  ReactEnvironmentInfo,
   ReactAsyncInfo,
+  ReactTimeInfo,
   ReactStackTrace,
   ReactCallSite,
 } from 'shared/ReactTypes';
@@ -2460,7 +2462,6 @@ function initializeFakeStack(
     const stack = debugInfo.stack;
     const env = debugInfo.env == null ? '' : debugInfo.env;
     // $FlowFixMe[cannot-write]
-    // $FlowFixMe[prop-missing]
     debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env);
   }
   if (debugInfo.owner != null) {
@@ -2472,7 +2473,11 @@ function initializeFakeStack(
 function resolveDebugInfo(
   response: Response,
   id: number,
-  debugInfo: ReactComponentInfo | ReactAsyncInfo,
+  debugInfo:
+    | ReactComponentInfo
+    | ReactEnvironmentInfo
+    | ReactAsyncInfo
+    | ReactTimeInfo,
 ): void {
   if (!__DEV__) {
     // 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(
   // to initialize it when we need it, we might be inside user code.
   const env =
     debugInfo.env === undefined ? response._rootEnvironmentName : debugInfo.env;
-  initializeFakeTask(response, debugInfo, env);
+  if (debugInfo.stack !== undefined) {
+    const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
+      // $FlowFixMe[incompatible-type]
+      debugInfo;
+    initializeFakeTask(response, componentInfoOrAsyncInfo, env);
+  }
   if (debugInfo.owner === null && response._debugRootOwner != null) {
-    // $FlowFixMe
-    debugInfo.owner = response._debugRootOwner;
+    // $FlowFixMe[prop-missing] By narrowing `owner` to `null`, we narrowed `debugInfo` to `ReactComponentInfo`
+    const componentInfo: ReactComponentInfo = debugInfo;
+    // $FlowFixMe[cannot-write]
+    componentInfo.owner = response._debugRootOwner;
     // We override the stack if we override the owner since the stack where the root JSX
     // was created on the server isn't very useful but where the request was made is.
-    // $FlowFixMe
-    debugInfo.debugStack = response._debugRootStack;
-  } else {
-    initializeFakeStack(response, debugInfo);
+    // $FlowFixMe[cannot-write]
+    componentInfo.debugStack = response._debugRootStack;
+  } else if (debugInfo.stack !== undefined) {
+    const componentInfoOrAsyncInfo: ReactComponentInfo | ReactAsyncInfo =
+      // $FlowFixMe[incompatible-type]
+      debugInfo;
+    initializeFakeStack(response, componentInfoOrAsyncInfo);
   }
 
   const chunk = getChunk(response, id);
@@ -2779,11 +2794,19 @@ function processFullStringRow(
     }
     case 68 /* "D" */: {
       if (__DEV__) {
-        const chunk: ResolvedModelChunk<ReactComponentInfo | ReactAsyncInfo> =
-          createResolvedModelChunk(response, row);
+        const chunk: ResolvedModelChunk<
+          | ReactComponentInfo
+          | ReactEnvironmentInfo
+          | ReactAsyncInfo
+          | ReactTimeInfo,
+        > = createResolvedModelChunk(response, row);
         initializeModelChunk(chunk);
-        const initializedChunk: SomeChunk<ReactComponentInfo | ReactAsyncInfo> =
-          chunk;
+        const initializedChunk: SomeChunk<
+          | ReactComponentInfo
+          | ReactEnvironmentInfo
+          | ReactAsyncInfo
+          | ReactTimeInfo,
+        > = chunk;
         if (initializedChunk.status === INITIALIZED) {
           resolveDebugInfo(response, id, initializedChunk.value);
         } else {

filename: packages/react-server/src/ReactFlightServer.js
patch: @@ -61,7 +61,9 @@ import type {
   RejectedThenable,
   ReactDebugInfo,
   ReactComponentInfo,
+  ReactEnvironmentInfo,
   ReactAsyncInfo,
+  ReactTimeInfo,
   ReactStackTrace,
   ReactCallSite,
 } from 'shared/ReactTypes';
@@ -3267,7 +3269,11 @@ function emitModelChunk(request: Request, id: number, json: string): void {
 function emitDebugChunk(
   request: Request,
   id: number,
-  debugInfo: ReactComponentInfo | ReactAsyncInfo,
+  debugInfo:
+    | ReactComponentInfo
+    | ReactAsyncInfo
+    | ReactEnvironmentInfo
+    | ReactTimeInfo,
 ): void {
   if (!__DEV__) {
     // These errors should never make it into a build so we don't need to encode them in codes.json

filename: packages/shared/ReactTypes.js
patch: @@ -188,7 +188,7 @@ export type ReactCallSite = [
 export type ReactStackTrace = Array<ReactCallSite>;
 
 export type ReactComponentInfo = {
-  +name?: string,
+  +name: string,
   +env?: string,
   +key?: null | string,
   +owner?: null | ReactComponentInfo,
@@ -199,10 +199,22 @@ export type ReactComponentInfo = {
   +debugTask?: null | ConsoleTask,
 };
 
+export type ReactEnvironmentInfo = {
+  +env: string,
+};
+
 export type ReactAsyncInfo = {
-  +started?: number,
-  +completed?: number,
+  +type: string,
+  // Stashed Data for the Specific Execution Environment. Not part of the transport protocol
+  +debugStack?: null | Error,
+  +debugTask?: null | ConsoleTask,
   +stack?: null | ReactStackTrace,
 };
 
-export type ReactDebugInfo = Array<ReactComponentInfo | ReactAsyncInfo>;
+export type ReactTimeInfo = {
+  +time: number, // performance.now
+};
+
+export type ReactDebugInfo = Array<
+  ReactComponentInfo | ReactEnvironmentInfo | ReactAsyncInfo | ReactTimeInfo,
+>;