Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions packages/react-client/src/__tests__/ReactFlight-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2822,7 +2822,7 @@ describe('ReactFlight', () => {
expect(getDebugInfo(promise)).toEqual(
__DEV__
? [
{time: 20},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 22 : 20},
{
name: 'ServerComponent',
env: 'Server',
Expand All @@ -2832,7 +2832,7 @@ describe('ReactFlight', () => {
transport: expect.arrayContaining([]),
},
},
{time: 21},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 23 : 21},
]
: undefined,
);
Expand All @@ -2843,46 +2843,46 @@ describe('ReactFlight', () => {
expect(getDebugInfo(thirdPartyChildren[0])).toEqual(
__DEV__
? [
{time: 22}, // Clamped to the start
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22}, // Clamped to the start
{
name: 'ThirdPartyComponent',
env: 'third-party',
key: null,
stack: ' in Object.<anonymous> (at **)',
props: {},
},
{time: 22},
{time: 23}, // This last one is when the promise resolved into the first party.
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 25 : 23}, // This last one is when the promise resolved into the first party.
]
: undefined,
);
expect(getDebugInfo(thirdPartyChildren[1])).toEqual(
__DEV__
? [
{time: 22}, // Clamped to the start
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22}, // Clamped to the start
{
name: 'ThirdPartyLazyComponent',
env: 'third-party',
key: null,
stack: ' in myLazy (at **)\n in lazyInitializer (at **)',
props: {},
},
{time: 22},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22},
]
: undefined,
);
expect(getDebugInfo(thirdPartyChildren[2])).toEqual(
__DEV__
? [
{time: 22},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22},
{
name: 'ThirdPartyFragmentComponent',
env: 'third-party',
key: '3',
stack: ' in Object.<anonymous> (at **)',
props: {},
},
{time: 22},
{time: gate(flags => flags.enableAsyncDebugInfo) ? 24 : 22},
]
: undefined,
);
Expand Down
87 changes: 86 additions & 1 deletion packages/react/src/ReactLazy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
* @flow
*/

import type {Wakeable, Thenable, ReactDebugInfo} from 'shared/ReactTypes';
import type {
Wakeable,
Thenable,
FulfilledThenable,
RejectedThenable,
ReactDebugInfo,
ReactIOInfo,
} from 'shared/ReactTypes';

import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';

import {REACT_LAZY_TYPE} from 'shared/ReactSymbols';

Expand All @@ -19,21 +28,25 @@ const Rejected = 2;
type UninitializedPayload<T> = {
_status: -1,
_result: () => Thenable<{default: T, ...}>,
_ioInfo?: ReactIOInfo, // DEV-only
};

type PendingPayload = {
_status: 0,
_result: Wakeable,
_ioInfo?: ReactIOInfo, // DEV-only
};

type ResolvedPayload<T> = {
_status: 1,
_result: {default: T, ...},
_ioInfo?: ReactIOInfo, // DEV-only
};

type RejectedPayload = {
_status: 2,
_result: mixed,
_ioInfo?: ReactIOInfo, // DEV-only
};

type Payload<T> =
Expand All @@ -51,6 +64,14 @@ export type LazyComponent<T, P> = {

function lazyInitializer<T>(payload: Payload<T>): T {
if (payload._status === Uninitialized) {
if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Mark when we first kicked off the lazy request.
// $FlowFixMe[cannot-write]
ioInfo.start = ioInfo.end = performance.now();
}
}
const ctor = payload._result;
const thenable = ctor();
// Transition to the next state.
Expand All @@ -68,6 +89,21 @@ function lazyInitializer<T>(payload: Payload<T>): T {
const resolved: ResolvedPayload<T> = (payload: any);
resolved._status = Resolved;
resolved._result = moduleObject;
if (__DEV__) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Mark the end time of when we resolved.
// $FlowFixMe[cannot-write]
ioInfo.end = performance.now();
}
// Make the thenable introspectable
if (thenable.status === undefined) {
const fulfilledThenable: FulfilledThenable<{default: T, ...}> =
(thenable: any);
fulfilledThenable.status = 'fulfilled';
fulfilledThenable.value = moduleObject;
}
}
}
},
error => {
Expand All @@ -79,9 +115,37 @@ function lazyInitializer<T>(payload: Payload<T>): T {
const rejected: RejectedPayload = (payload: any);
rejected._status = Rejected;
rejected._result = error;
if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Mark the end time of when we rejected.
// $FlowFixMe[cannot-write]
ioInfo.end = performance.now();
}
// Make the thenable introspectable
if (thenable.status === undefined) {
const rejectedThenable: RejectedThenable<{default: T, ...}> =
(thenable: any);
rejectedThenable.status = 'rejected';
rejectedThenable.reason = error;
}
}
}
},
);
if (__DEV__ && enableAsyncDebugInfo) {
const ioInfo = payload._ioInfo;
if (ioInfo != null) {
// Stash the thenable for introspection of the value later.
// $FlowFixMe[cannot-write]
ioInfo.value = thenable;
const displayName = thenable.displayName;
if (typeof displayName === 'string') {
// $FlowFixMe[cannot-write]
ioInfo.name = displayName;
}
}
}
if (payload._status === Uninitialized) {
// In case, we're still uninitialized, then we're waiting for the thenable
// to resolve. Set it as pending in the meantime.
Expand Down Expand Up @@ -140,5 +204,26 @@ export function lazy<T>(
_init: lazyInitializer,
};

if (__DEV__ && enableAsyncDebugInfo) {
// TODO: We should really track the owner here but currently ReactIOInfo
// can only contain ReactComponentInfo and not a Fiber. It's unusual to
// create a lazy inside an owner though since they should be in module scope.
const owner = null;
const ioInfo: ReactIOInfo = {
name: 'lazy',
start: -1,
end: -1,
value: null,
owner: owner,
debugStack: new Error('react-stack-top-frame'),
// eslint-disable-next-line react-internal/no-production-logging
debugTask: console.createTask ? console.createTask('lazy()') : null,
};
payload._ioInfo = ioInfo;
// Add debug info to the lazy, but this doesn't have an await stack yet.
// That will be inferred by later usage.
lazyType._debugInfo = [{awaited: ioInfo}];
}

return lazyType;
}
1 change: 1 addition & 0 deletions packages/shared/ReactTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ interface ThenableImpl<T> {
onFulfill: (value: T) => mixed,
onReject: (error: mixed) => mixed,
): void | Wakeable;
displayName?: string;
}
interface UntrackedThenable<T> extends ThenableImpl<T> {
status?: void;
Expand Down
Loading