diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index eb354aba58753..49968b33590d9 100644
--- a/packages/react-client/src/__tests__/ReactFlight-test.js
+++ b/packages/react-client/src/__tests__/ReactFlight-test.js
@@ -2991,6 +2991,64 @@ describe('ReactFlight', () => {
);
});
+ // @gate !__DEV__ || enableComponentPerformanceTrack
+ it('preserves debug info for server-to-server through use()', async () => {
+ function ThirdPartyComponent() {
+ return 'hi';
+ }
+
+ function ServerComponent({transport}) {
+ // This is a Server Component that receives other Server Components from a third party.
+ const text = ReactServer.use(ReactNoopFlightClient.read(transport));
+ return
{text.toUpperCase()}
;
+ }
+
+ const thirdPartyTransport = ReactNoopFlightServer.render(
+ ,
+ {
+ environmentName: 'third-party',
+ },
+ );
+
+ const transport = ReactNoopFlightServer.render(
+ ,
+ );
+
+ await act(async () => {
+ const promise = ReactNoopFlightClient.read(transport);
+ expect(getDebugInfo(promise)).toEqual(
+ __DEV__
+ ? [
+ {time: 16},
+ {
+ name: 'ServerComponent',
+ env: 'Server',
+ key: null,
+ stack: ' in Object. (at **)',
+ props: {
+ transport: expect.arrayContaining([]),
+ },
+ },
+ {time: 16},
+ {
+ name: 'ThirdPartyComponent',
+ env: 'third-party',
+ key: null,
+ stack: ' in Object. (at **)',
+ props: {},
+ },
+ {time: 16},
+ {time: 17},
+ ]
+ : undefined,
+ );
+ const result = await promise;
+ ReactNoop.render(result);
+ });
+
+ expect(ReactNoop).toMatchRenderedOutput(HI
);
+ });
+
it('preserves error stacks passed through server-to-server with source maps', async () => {
async function ServerComponent({transport}) {
// This is a Server Component that receives other Server Components from a third party.
diff --git a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
index 9d00b39efad25..baa297cf33d41 100644
--- a/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
+++ b/packages/react-server-dom-turbopack/src/ReactFlightTurbopackReferences.js
@@ -161,6 +161,9 @@ const deepProxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
+ // React looks for debugInfo on thenables.
+ case '_debugInfo':
+ return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
@@ -210,6 +213,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe {
// reference.
case 'defaultProps':
return undefined;
+ // React looks for debugInfo on thenables.
+ case '_debugInfo':
+ return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
diff --git a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
index 60fe34b1c08df..c06e52a578ddb 100644
--- a/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
+++ b/packages/react-server-dom-webpack/src/ReactFlightWebpackReferences.js
@@ -162,6 +162,9 @@ const deepProxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
+ // React looks for debugInfo on thenables.
+ case '_debugInfo':
+ return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
@@ -211,6 +214,9 @@ function getReference(target: Function, name: string | symbol): $FlowFixMe {
// reference.
case 'defaultProps':
return undefined;
+ // React looks for debugInfo on thenables.
+ case '_debugInfo':
+ return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js
index c5ffc5dd70342..ed369be0e9b18 100644
--- a/packages/react-server/src/ReactFlightHooks.js
+++ b/packages/react-server/src/ReactFlightHooks.js
@@ -58,6 +58,12 @@ export function getThenableStateAfterSuspending(): ThenableState {
return state;
}
+export function getTrackedThenablesAfterRendering(): null | Array<
+ Thenable,
+> {
+ return thenableState;
+}
+
export const HooksDispatcher: Dispatcher = {
readContext: (unsupportedContext: any),
diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js
index 3a56148f7101d..e51197c5b2b19 100644
--- a/packages/react-server/src/ReactFlightServer.js
+++ b/packages/react-server/src/ReactFlightServer.js
@@ -91,6 +91,7 @@ import {
initAsyncDebugInfo,
markAsyncSequenceRootTask,
getCurrentAsyncSequence,
+ getAsyncSequenceFromPromise,
parseStackTrace,
supportsComponentStorage,
componentStorage,
@@ -106,6 +107,7 @@ import {
prepareToUseHooksForRequest,
prepareToUseHooksForComponent,
getThenableStateAfterSuspending,
+ getTrackedThenablesAfterRendering,
resetHooksForRequest,
} from './ReactFlightHooks';
import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
@@ -690,26 +692,14 @@ function serializeThenable(
switch (thenable.status) {
case 'fulfilled': {
- if (__DEV__) {
- // If this came from Flight, forward any debug info into this new row.
- const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
- if (debugInfo) {
- forwardDebugInfo(request, newTask, debugInfo);
- }
- }
+ forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
// We have the resolved value, we can go ahead and schedule it for serialization.
newTask.model = thenable.value;
pingTask(request, newTask);
return newTask.id;
}
case 'rejected': {
- if (__DEV__) {
- // If this came from Flight, forward any debug info into this new row.
- const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
- if (debugInfo) {
- forwardDebugInfo(request, newTask, debugInfo);
- }
- }
+ forwardDebugInfoFromThenable(request, newTask, thenable, null, null);
const x = thenable.reason;
erroredTask(request, newTask, x);
return newTask.id;
@@ -758,24 +748,11 @@ function serializeThenable(
thenable.then(
value => {
- if (__DEV__) {
- // If this came from Flight, forward any debug info into this new row.
- const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
- if (debugInfo) {
- forwardDebugInfo(request, newTask, debugInfo);
- }
- }
+ forwardDebugInfoFromCurrentContext(request, newTask, thenable);
newTask.model = value;
pingTask(request, newTask);
},
reason => {
- if (__DEV__) {
- // If this came from Flight, forward any debug info into this new row.
- const debugInfo: ?ReactDebugInfo = (thenable: any)._debugInfo;
- if (debugInfo) {
- forwardDebugInfo(request, newTask, debugInfo);
- }
- }
if (newTask.status === PENDING) {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If this is async we need to time when this task finishes.
@@ -1055,13 +1032,21 @@ function readThenable(thenable: Thenable): T {
throw thenable;
}
-function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
+function createLazyWrapperAroundWakeable(
+ request: Request,
+ task: Task,
+ wakeable: Wakeable,
+) {
// This is a temporary fork of the `use` implementation until we accept
// promises everywhere.
const thenable: Thenable = (wakeable: any);
switch (thenable.status) {
- case 'fulfilled':
+ case 'fulfilled': {
+ forwardDebugInfoFromThenable(request, task, thenable, null, null);
+ return thenable.value;
+ }
case 'rejected':
+ forwardDebugInfoFromThenable(request, task, thenable, null, null);
break;
default: {
if (typeof thenable.status === 'string') {
@@ -1074,6 +1059,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
pendingThenable.status = 'pending';
pendingThenable.then(
fulfilledValue => {
+ forwardDebugInfoFromCurrentContext(request, task, thenable);
if (thenable.status === 'pending') {
const fulfilledThenable: FulfilledThenable = (thenable: any);
fulfilledThenable.status = 'fulfilled';
@@ -1081,6 +1067,7 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
}
},
(error: mixed) => {
+ forwardDebugInfoFromCurrentContext(request, task, thenable);
if (thenable.status === 'pending') {
const rejectedThenable: RejectedThenable = (thenable: any);
rejectedThenable.status = 'rejected';
@@ -1096,10 +1083,6 @@ function createLazyWrapperAroundWakeable(wakeable: Wakeable) {
_payload: thenable,
_init: readThenable,
};
- if (__DEV__) {
- // If this came from React, transfer the debug info.
- lazyType._debugInfo = (thenable: any)._debugInfo || [];
- }
return lazyType;
}
@@ -1178,12 +1161,9 @@ function processServerComponentReturnValue(
}
}, voidHandler);
}
- if (thenable.status === 'fulfilled') {
- return thenable.value;
- }
// TODO: Once we accept Promises as children on the client, we can just return
// the thenable here.
- return createLazyWrapperAroundWakeable(result);
+ return createLazyWrapperAroundWakeable(request, task, result);
}
if (__DEV__) {
@@ -1386,6 +1366,7 @@ function renderFunctionComponent(
}
}
} else {
+ componentDebugInfo = (null: any);
prepareToUseHooksForComponent(prevThenableState, null);
// The secondArg is always undefined in Server Components since refs error early.
const secondArg = undefined;
@@ -1408,6 +1389,34 @@ function renderFunctionComponent(
throw null;
}
+ if (
+ __DEV__ ||
+ (enableProfilerTimer &&
+ enableComponentPerformanceTrack &&
+ enableAsyncDebugInfo)
+ ) {
+ // Forward any debug information for any Promises that we use():ed during the render.
+ // We do this at the end so that we don't keep doing this for each retry.
+ const trackedThenables = getTrackedThenablesAfterRendering();
+ if (trackedThenables !== null) {
+ const stacks: Array =
+ __DEV__ && enableAsyncDebugInfo
+ ? (trackedThenables: any)._stacks ||
+ ((trackedThenables: any)._stacks = [])
+ : (null: any);
+ for (let i = 0; i < trackedThenables.length; i++) {
+ const stack = __DEV__ && enableAsyncDebugInfo ? stacks[i] : null;
+ forwardDebugInfoFromThenable(
+ request,
+ task,
+ trackedThenables[i],
+ __DEV__ ? componentDebugInfo : null,
+ stack,
+ );
+ }
+ }
+ }
+
// Apply special cases.
result = processServerComponentReturnValue(request, task, Component, result);
@@ -1884,7 +1893,7 @@ function visitAsyncNode(
request: Request,
task: Task,
node: AsyncSequence,
- visited: Set,
+ visited: Set,
cutOff: number,
): null | PromiseNode | IONode {
if (visited.has(node)) {
@@ -1943,7 +1952,8 @@ function visitAsyncNode(
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
// the thing that generated this node and its virtual children.
const debugInfo = node.debugInfo;
- if (debugInfo !== null) {
+ if (debugInfo !== null && !visited.has(debugInfo)) {
+ visited.add(debugInfo);
forwardDebugInfo(request, task, debugInfo);
}
return match;
@@ -2003,8 +2013,9 @@ function visitAsyncNode(
}
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
// the thing that generated this node and its virtual children.
- const debugInfo: null | ReactDebugInfo = node.debugInfo;
- if (debugInfo !== null) {
+ const debugInfo = node.debugInfo;
+ if (debugInfo !== null && !visited.has(debugInfo)) {
+ visited.add(debugInfo);
forwardDebugInfo(request, task, debugInfo);
}
return match;
@@ -2020,8 +2031,14 @@ function emitAsyncSequence(
request: Request,
task: Task,
node: AsyncSequence,
+ alreadyForwardedDebugInfo: ?ReactDebugInfo,
+ owner: null | ReactComponentInfo,
+ stack: null | Error,
): void {
- const visited: Set = new Set();
+ const visited: Set = new Set();
+ if (__DEV__ && alreadyForwardedDebugInfo) {
+ visited.add(alreadyForwardedDebugInfo);
+ }
const awaitedNode = visitAsyncNode(request, task, node, visited, task.time);
if (awaitedNode !== null) {
// Nothing in user space (unfiltered stack) awaited this.
@@ -2032,10 +2049,21 @@ function emitAsyncSequence(
const env = (0, request.environmentName)();
// If we don't have any thing awaited, the time we started awaiting was internal
// when we yielded after rendering. The current task time is basically that.
- emitDebugChunk(request, task.id, {
+ const debugInfo: ReactAsyncInfo = {
awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference.
env: env,
- });
+ };
+ if (__DEV__) {
+ if (owner != null) {
+ // $FlowFixMe[cannot-write]
+ debugInfo.owner = owner;
+ }
+ if (stack != null) {
+ // $FlowFixMe[cannot-write]
+ debugInfo.stack = filterStackTrace(request, parseStackTrace(stack, 1));
+ }
+ }
+ emitDebugChunk(request, task.id, debugInfo);
markOperationEndTime(request, task, awaitedNode.end);
}
}
@@ -2044,12 +2072,6 @@ function pingTask(request: Request, task: Task): void {
if (enableProfilerTimer && enableComponentPerformanceTrack) {
// If this was async we need to emit the time when it completes.
task.timed = true;
- if (enableAsyncDebugInfo) {
- const sequence = getCurrentAsyncSequence();
- if (sequence !== null) {
- emitAsyncSequence(request, task, sequence);
- }
- }
}
const pingedTasks = request.pingedTasks;
pingedTasks.push(task);
@@ -4316,6 +4338,58 @@ function forwardDebugInfo(
}
}
+function forwardDebugInfoFromThenable(
+ request: Request,
+ task: Task,
+ thenable: Thenable,
+ owner: null | ReactComponentInfo, // DEV-only
+ stack: null | Error, // DEV-only
+): void {
+ let debugInfo: ?ReactDebugInfo;
+ if (__DEV__) {
+ // If this came from Flight, forward any debug info into this new row.
+ debugInfo = thenable._debugInfo;
+ if (debugInfo) {
+ forwardDebugInfo(request, task, debugInfo);
+ }
+ }
+ if (
+ enableProfilerTimer &&
+ enableComponentPerformanceTrack &&
+ enableAsyncDebugInfo
+ ) {
+ const sequence = getAsyncSequenceFromPromise(thenable);
+ if (sequence !== null) {
+ emitAsyncSequence(request, task, sequence, debugInfo, owner, stack);
+ }
+ }
+}
+
+function forwardDebugInfoFromCurrentContext(
+ request: Request,
+ task: Task,
+ thenable: Thenable,
+): void {
+ let debugInfo: ?ReactDebugInfo;
+ if (__DEV__) {
+ // If this came from Flight, forward any debug info into this new row.
+ debugInfo = thenable._debugInfo;
+ if (debugInfo) {
+ forwardDebugInfo(request, task, debugInfo);
+ }
+ }
+ if (
+ enableProfilerTimer &&
+ enableComponentPerformanceTrack &&
+ enableAsyncDebugInfo
+ ) {
+ const sequence = getCurrentAsyncSequence();
+ if (sequence !== null) {
+ emitAsyncSequence(request, task, sequence, debugInfo, null, null);
+ }
+ }
+}
+
function emitTimingChunk(
request: Request,
id: number,
diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNode.js b/packages/react-server/src/ReactFlightServerConfigDebugNode.js
index ddcf256525b6d..46a29fb3cb2ef 100644
--- a/packages/react-server/src/ReactFlightServerConfigDebugNode.js
+++ b/packages/react-server/src/ReactFlightServerConfigDebugNode.js
@@ -24,9 +24,12 @@ import {
UNRESOLVED_AWAIT_NODE,
} from './ReactFlightAsyncSequence';
import {resolveOwner} from './flight/ReactFlightCurrentOwner';
-import {createHook, executionAsyncId} from 'async_hooks';
+import {createHook, executionAsyncId, AsyncResource} from 'async_hooks';
import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
+// $FlowFixMe[method-unbinding]
+const getAsyncId = AsyncResource.prototype.asyncId;
+
const pendingOperations: Map =
__DEV__ && enableAsyncDebugInfo ? new Map() : (null: any);
@@ -260,3 +263,29 @@ export function getCurrentAsyncSequence(): null | AsyncSequence {
}
return currentNode;
}
+
+export function getAsyncSequenceFromPromise(
+ promise: any,
+): null | AsyncSequence {
+ if (!__DEV__ || !enableAsyncDebugInfo) {
+ return null;
+ }
+ // A Promise is conceptually an AsyncResource but doesn't have its own methods.
+ // We use this hack to extract the internal asyncId off the Promise.
+ let asyncId: void | number;
+ try {
+ asyncId = getAsyncId.call(promise);
+ } catch (x) {
+ // Ignore errors extracting the ID. We treat it as missing.
+ // This could happen if our hack stops working or in the case where this is
+ // a Proxy that throws such as our own ClientReference proxies.
+ }
+ if (asyncId === undefined) {
+ return null;
+ }
+ const node = pendingOperations.get(asyncId);
+ if (node === undefined) {
+ return null;
+ }
+ return node;
+}
diff --git a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js
index 7418aaef18310..e435929114b57 100644
--- a/packages/react-server/src/ReactFlightServerConfigDebugNoop.js
+++ b/packages/react-server/src/ReactFlightServerConfigDebugNoop.js
@@ -15,3 +15,8 @@ export function markAsyncSequenceRootTask(): void {}
export function getCurrentAsyncSequence(): null | AsyncSequence {
return null;
}
+export function getAsyncSequenceFromPromise(
+ promise: any,
+): null | AsyncSequence {
+ return null;
+}
diff --git a/packages/react-server/src/ReactFlightServerTemporaryReferences.js b/packages/react-server/src/ReactFlightServerTemporaryReferences.js
index 1f6b9f8ee3611..e368b2e800795 100644
--- a/packages/react-server/src/ReactFlightServerTemporaryReferences.js
+++ b/packages/react-server/src/ReactFlightServerTemporaryReferences.js
@@ -52,6 +52,9 @@ const proxyHandlers = {
// reference.
case 'defaultProps':
return undefined;
+ // React looks for debugInfo on thenables.
+ case '_debugInfo':
+ return undefined;
// Avoid this attempting to be serialized.
case 'toJSON':
return undefined;
diff --git a/packages/react-server/src/ReactFlightThenable.js b/packages/react-server/src/ReactFlightThenable.js
index 47e7b914da176..99ddb36fa5995 100644
--- a/packages/react-server/src/ReactFlightThenable.js
+++ b/packages/react-server/src/ReactFlightThenable.js
@@ -20,9 +20,11 @@ import type {
RejectedThenable,
} from 'shared/ReactTypes';
+import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
+
import noop from 'shared/noop';
-export opaque type ThenableState = Array>;
+export type ThenableState = Array>;
// An error that is thrown (e.g. by `use`) to trigger Suspense. If we
// detect this is caught by userspace, we'll log a warning in development.
@@ -50,6 +52,11 @@ export function trackUsedThenable(
const previous = thenableState[index];
if (previous === undefined) {
thenableState.push(thenable);
+ if (__DEV__ && enableAsyncDebugInfo) {
+ const stacks: Array =
+ (thenableState: any)._stacks || ((thenableState: any)._stacks = []);
+ stacks.push(new Error());
+ }
} else {
if (previous !== thenable) {
// Reuse the previous thenable, and drop the new one. We can assume
diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
index 05f31ff7def9d..42fa56a836a2d 100644
--- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
+++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js
@@ -495,6 +495,267 @@ describe('ReactFlightAsyncDebugInfo', () => {
}
});
+ it('can track async information when use()d', async () => {
+ async function getData(text) {
+ await delay(1);
+ return text.toUpperCase();
+ }
+
+ function Component() {
+ const result = ReactServer.use(getData('hi'));
+ const moreData = getData('seb');
+ return ;
+ }
+
+ function InnerComponent({text, promise}) {
+ // This async function depends on the I/O in parent components but it should not
+ // include that I/O as part of its own meta data.
+ return text + ', ' + ReactServer.use(promise);
+ }
+
+ const stream = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ {},
+ {
+ filterStackFrame,
+ },
+ );
+
+ const readable = new Stream.PassThrough(streamOptions);
+
+ const result = ReactServerDOMClient.createFromNodeStream(readable, {
+ moduleMap: {},
+ moduleLoading: {},
+ });
+ stream.pipe(readable);
+
+ expect(await result).toBe('HI, SEB');
+ if (
+ __DEV__ &&
+ gate(
+ flags =>
+ flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo,
+ )
+ ) {
+ expect(getDebugInfo(result)).toMatchInlineSnapshot(`
+ [
+ {
+ "time": 0,
+ },
+ {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 517,
+ 40,
+ 498,
+ 49,
+ ],
+ ],
+ },
+ {
+ "time": 0,
+ },
+ {
+ "awaited": {
+ "end": 0,
+ "env": "Server",
+ "name": "delay",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 517,
+ 40,
+ 498,
+ 49,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "delay",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 133,
+ 12,
+ 132,
+ 3,
+ ],
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 500,
+ 13,
+ 499,
+ 5,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 505,
+ 36,
+ 504,
+ 5,
+ ],
+ ],
+ "start": 0,
+ },
+ "env": "Server",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 517,
+ 40,
+ 498,
+ 49,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 500,
+ 13,
+ 499,
+ 5,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 505,
+ 36,
+ 504,
+ 5,
+ ],
+ ],
+ },
+ {
+ "time": 0,
+ },
+ {
+ "time": 0,
+ },
+ {
+ "env": "Server",
+ "key": null,
+ "name": "InnerComponent",
+ "props": {},
+ "stack": [
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 507,
+ 60,
+ 504,
+ 5,
+ ],
+ ],
+ },
+ {
+ "awaited": {
+ "end": 0,
+ "env": "Server",
+ "name": "delay",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 517,
+ 40,
+ 498,
+ 49,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "delay",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 133,
+ 12,
+ 132,
+ 3,
+ ],
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 500,
+ 13,
+ 499,
+ 5,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 506,
+ 22,
+ 504,
+ 5,
+ ],
+ ],
+ "start": 0,
+ },
+ "env": "Server",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "InnerComponent",
+ "props": {},
+ "stack": [
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 507,
+ 60,
+ 504,
+ 5,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "InnerComponent",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 513,
+ 40,
+ 510,
+ 5,
+ ],
+ ],
+ },
+ {
+ "time": 0,
+ },
+ {
+ "time": 0,
+ },
+ ]
+ `);
+ }
+ });
+
it('can track the start of I/O when no native promise is used', async () => {
function Component() {
const callbacks = [];
@@ -540,9 +801,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 511,
+ 772,
109,
- 498,
+ 759,
67,
],
],
@@ -561,9 +822,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 511,
+ 772,
109,
- 498,
+ 759,
67,
],
],
@@ -572,9 +833,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 501,
+ 762,
7,
- 499,
+ 760,
5,
],
],
@@ -634,9 +895,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 605,
+ 866,
109,
- 596,
+ 857,
94,
],
],
@@ -705,9 +966,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 676,
+ 937,
109,
- 652,
+ 913,
50,
],
],
@@ -787,9 +1048,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 758,
+ 1019,
109,
- 741,
+ 1002,
63,
],
],
@@ -814,9 +1075,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 754,
+ 1015,
24,
- 753,
+ 1014,
5,
],
],
@@ -846,9 +1107,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 754,
+ 1015,
24,
- 753,
+ 1014,
5,
],
],
@@ -865,17 +1126,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 743,
+ 1004,
13,
- 742,
+ 1003,
5,
],
[
"ThirdPartyComponent",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 749,
+ 1010,
24,
- 748,
+ 1009,
5,
],
],
@@ -899,9 +1160,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 754,
+ 1015,
24,
- 753,
+ 1014,
5,
],
],
@@ -910,17 +1171,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 743,
+ 1004,
13,
- 742,
+ 1003,
5,
],
[
"ThirdPartyComponent",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 749,
+ 1010,
24,
- 748,
+ 1009,
5,
],
],
@@ -953,9 +1214,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 754,
+ 1015,
24,
- 753,
+ 1014,
5,
],
],
@@ -972,17 +1233,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 744,
+ 1005,
13,
- 742,
+ 1003,
5,
],
[
"ThirdPartyComponent",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 749,
+ 1010,
18,
- 748,
+ 1009,
5,
],
],
@@ -1006,9 +1267,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 754,
+ 1015,
24,
- 753,
+ 1014,
5,
],
],
@@ -1017,17 +1278,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 744,
+ 1005,
13,
- 742,
+ 1003,
5,
],
[
"ThirdPartyComponent",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 749,
+ 1010,
18,
- 748,
+ 1009,
5,
],
],
@@ -1047,12 +1308,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
});
it('can track cached entries awaited in later components', async () => {
- let cacheKey;
- let cacheValue;
const getData = cache(async function getData(text) {
- if (cacheKey === text) {
- return cacheValue;
- }
await delay(1);
return text.toUpperCase();
});
@@ -1105,9 +1361,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1071,
+ 1327,
40,
- 1049,
+ 1310,
62,
],
],
@@ -1129,9 +1385,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1071,
+ 1327,
40,
- 1049,
+ 1310,
62,
],
],
@@ -1148,17 +1404,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1056,
+ 1312,
13,
- 1052,
+ 1311,
25,
],
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1066,
+ 1322,
13,
- 1065,
+ 1321,
5,
],
],
@@ -1174,9 +1430,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1071,
+ 1327,
40,
- 1049,
+ 1310,
62,
],
],
@@ -1185,17 +1441,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1056,
+ 1312,
13,
- 1052,
+ 1311,
25,
],
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1066,
+ 1322,
13,
- 1065,
+ 1321,
5,
],
],
@@ -1215,9 +1471,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1067,
+ 1323,
60,
- 1065,
+ 1321,
5,
],
],
@@ -1239,9 +1495,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1071,
+ 1327,
40,
- 1049,
+ 1310,
62,
],
],
@@ -1258,17 +1514,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"getData",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1056,
+ 1312,
13,
- 1052,
+ 1311,
25,
],
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1066,
+ 1322,
13,
- 1065,
+ 1321,
5,
],
],
@@ -1284,9 +1540,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Component",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1067,
+ 1323,
60,
- 1065,
+ 1321,
5,
],
],
@@ -1295,9 +1551,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Child",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1061,
+ 1317,
28,
- 1060,
+ 1316,
5,
],
],
@@ -1313,6 +1569,238 @@ describe('ReactFlightAsyncDebugInfo', () => {
}
});
+ it('can track cached entries used in child position', async () => {
+ const getData = cache(async function getData(text) {
+ await delay(1);
+ return text.toUpperCase();
+ });
+
+ function Child() {
+ return getData('hi');
+ }
+
+ function Component() {
+ ReactServer.use(getData('hi'));
+ return ;
+ }
+
+ const stream = ReactServerDOMServer.renderToPipeableStream(
+ ,
+ {},
+ {
+ filterStackFrame,
+ },
+ );
+
+ const readable = new Stream.PassThrough(streamOptions);
+
+ const result = ReactServerDOMClient.createFromNodeStream(readable, {
+ moduleMap: {},
+ moduleLoading: {},
+ });
+ stream.pipe(readable);
+
+ expect(await result).toBe('HI');
+ if (
+ __DEV__ &&
+ gate(
+ flags =>
+ flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo,
+ )
+ ) {
+ expect(getDebugInfo(result)).toMatchInlineSnapshot(`
+ [
+ {
+ "time": 0,
+ },
+ {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1588,
+ 40,
+ 1572,
+ 57,
+ ],
+ ],
+ },
+ {
+ "time": 0,
+ },
+ {
+ "awaited": {
+ "end": 0,
+ "env": "Server",
+ "name": "delay",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1588,
+ 40,
+ 1572,
+ 57,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "delay",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 133,
+ 12,
+ 132,
+ 3,
+ ],
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1574,
+ 13,
+ 1573,
+ 25,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1583,
+ 23,
+ 1582,
+ 5,
+ ],
+ ],
+ "start": 0,
+ },
+ "env": "Server",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1588,
+ 40,
+ 1572,
+ 57,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1574,
+ 13,
+ 1573,
+ 25,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1583,
+ 23,
+ 1582,
+ 5,
+ ],
+ ],
+ },
+ {
+ "time": 0,
+ },
+ {
+ "time": 0,
+ },
+ {
+ "env": "Server",
+ "key": null,
+ "name": "Child",
+ "props": {},
+ "stack": [
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1584,
+ 60,
+ 1582,
+ 5,
+ ],
+ ],
+ },
+ {
+ "awaited": {
+ "end": 0,
+ "env": "Server",
+ "name": "delay",
+ "owner": {
+ "env": "Server",
+ "key": null,
+ "name": "Component",
+ "props": {},
+ "stack": [
+ [
+ "Object.",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1588,
+ 40,
+ 1572,
+ 57,
+ ],
+ ],
+ },
+ "stack": [
+ [
+ "delay",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 133,
+ 12,
+ 132,
+ 3,
+ ],
+ [
+ "getData",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1574,
+ 13,
+ 1573,
+ 25,
+ ],
+ [
+ "Component",
+ "/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
+ 1583,
+ 23,
+ 1582,
+ 5,
+ ],
+ ],
+ "start": 0,
+ },
+ "env": "Server",
+ },
+ {
+ "time": 0,
+ },
+ {
+ "time": 0,
+ },
+ ]
+ `);
+ }
+ });
+
it('can track implicit returned promises that are blocked by previous data', async () => {
async function delayTwice() {
await delay('', 20);
@@ -1368,9 +1856,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1392,9 +1880,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1411,17 +1899,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTrice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1324,
+ 1812,
13,
- 1322,
+ 1810,
5,
],
[
"Bar",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1329,
+ 1817,
13,
- 1328,
+ 1816,
5,
],
],
@@ -1437,9 +1925,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1448,17 +1936,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTrice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1324,
+ 1812,
13,
- 1322,
+ 1810,
5,
],
[
"Bar",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1329,
+ 1817,
13,
- 1328,
+ 1816,
5,
],
],
@@ -1480,9 +1968,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1499,25 +1987,25 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTwice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1318,
+ 1806,
13,
- 1317,
+ 1805,
5,
],
[
"delayTrice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1323,
+ 1811,
15,
- 1322,
+ 1810,
5,
],
[
"Bar",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1329,
+ 1817,
13,
- 1328,
+ 1816,
5,
],
],
@@ -1533,9 +2021,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1544,25 +2032,25 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTwice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1318,
+ 1806,
13,
- 1317,
+ 1805,
5,
],
[
"delayTrice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1323,
+ 1811,
15,
- 1322,
+ 1810,
5,
],
[
"Bar",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1329,
+ 1817,
13,
- 1328,
+ 1816,
5,
],
],
@@ -1584,9 +2072,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1603,9 +2091,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTwice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1319,
+ 1807,
13,
- 1317,
+ 1805,
5,
],
],
@@ -1621,9 +2109,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"Object.",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1334,
+ 1822,
40,
- 1316,
+ 1804,
80,
],
],
@@ -1632,9 +2120,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
[
"delayTwice",
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
- 1319,
+ 1807,
13,
- 1317,
+ 1805,
5,
],
],
diff --git a/scripts/flow/environment.js b/scripts/flow/environment.js
index d66ef65d9d318..39c792b449277 100644
--- a/scripts/flow/environment.js
+++ b/scripts/flow/environment.js
@@ -356,7 +356,9 @@ declare module 'async_hooks' {
run(store: T, callback: (...args: any[]) => R, ...args: any[]): R;
enterWith(store: T): void;
}
- declare interface AsyncResource {}
+ declare class AsyncResource {
+ asyncId(): number;
+ }
declare function executionAsyncId(): number;
declare function executionAsyncResource(): AsyncResource;
declare function triggerAsyncId(): number;