Skip to content

Commit bdc9052

Browse files
committed
Transfer debug info in wakeChunk and initializeModelChunk
1 parent 21e5795 commit bdc9052

File tree

4 files changed

+168
-141
lines changed

4 files changed

+168
-141
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 123 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,27 @@ function wakeChunk<T>(
512512
fulfillReference(listener, value, chunk);
513513
}
514514
}
515+
516+
if (__DEV__ && chunk.status === INITIALIZED) {
517+
const resolvedValue = resolveLazy(value);
518+
if (isReactElementOrArrayLike(resolvedValue) || isLazy(resolvedValue)) {
519+
const debugInfo = chunk._debugInfo.splice(0);
520+
if (resolvedValue._debugInfo) {
521+
// $FlowFixMe[method-unbinding]
522+
resolvedValue._debugInfo.push.apply(
523+
resolvedValue._debugInfo,
524+
debugInfo,
525+
);
526+
} else {
527+
Object.defineProperty(resolvedValue, '_debugInfo', {
528+
configurable: false,
529+
enumerable: false,
530+
writable: true,
531+
value: debugInfo,
532+
});
533+
}
534+
}
535+
}
515536
}
516537

517538
function rejectChunk(
@@ -959,6 +980,24 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
959980
return;
960981
}
961982
}
983+
984+
if (__DEV__) {
985+
if (isReactElementOrArrayLike(value)) {
986+
const debugInfo = chunk._debugInfo.splice(0);
987+
if (value._debugInfo) {
988+
// $FlowFixMe[method-unbinding]
989+
value._debugInfo.push.apply(value._debugInfo, debugInfo);
990+
} else {
991+
Object.defineProperty(value, '_debugInfo', {
992+
configurable: false,
993+
enumerable: false,
994+
writable: true,
995+
value: debugInfo,
996+
});
997+
}
998+
}
999+
}
1000+
9621001
const initializedChunk: InitializedChunk<T> = (chunk: any);
9631002
initializedChunk.status = INITIALIZED;
9641003
initializedChunk.value = value;
@@ -1052,11 +1091,7 @@ function getTaskName(type: mixed): string {
10521091
// the client. There should only be one for any given owner chain.
10531092
return '"use client"';
10541093
}
1055-
if (
1056-
typeof type === 'object' &&
1057-
type !== null &&
1058-
type.$$typeof === REACT_LAZY_TYPE
1059-
) {
1094+
if (isLazy(type)) {
10601095
if (type._init === readChunk) {
10611096
// This is a lazy node created by Flight. It is probably a client reference.
10621097
// We use the "use client" string to indicate that this is the boundary into
@@ -1168,7 +1203,6 @@ function initializeElement(
11681203

11691204
function createElement(
11701205
response: Response,
1171-
isRoot: boolean,
11721206
type: mixed,
11731207
key: mixed,
11741208
props: mixed,
@@ -1277,19 +1311,10 @@ function createElement(
12771311
// a Lazy node referencing this Element to let everything around it proceed.
12781312
const blockedChunk: BlockedChunk<React$Element<any>> =
12791313
createBlockedChunk(response);
1280-
if (__DEV__) {
1281-
// If this is the root element, forward the live debug info of the
1282-
// initializing chunk to the blocked chunk.
1283-
if (isRoot && initializingChunk !== null) {
1284-
blockedChunk._debugInfo = initializingChunk._debugInfo;
1285-
}
1286-
}
12871314
handler.value = element;
12881315
handler.chunk = blockedChunk;
12891316
const lazyNode = createLazyChunkWrapper(blockedChunk, validated);
12901317
if (__DEV__) {
1291-
// Forward the live debug info of the lazy node to the element.
1292-
element._debugInfo = lazyNode._debugInfo;
12931318
// After we have initialized any blocked references, initialize stack etc.
12941319
const init = initializeElement.bind(null, response, element, lazyNode);
12951320
blockedChunk.then(init, init);
@@ -1346,11 +1371,7 @@ function fulfillReference(
13461371
const {response, handler, parentObject, key, map, path} = reference;
13471372

13481373
for (let i = 1; i < path.length; i++) {
1349-
while (
1350-
typeof value === 'object' &&
1351-
value !== null &&
1352-
value.$$typeof === REACT_LAZY_TYPE
1353-
) {
1374+
while (isLazy(value)) {
13541375
// We never expect to see a Lazy node on this path because we encode those as
13551376
// separate models. This must mean that we have inserted an extra lazy node
13561377
// e.g. to replace a blocked element. We must instead look for it inside.
@@ -1422,11 +1443,7 @@ function fulfillReference(
14221443
value = value[path[i]];
14231444
}
14241445

1425-
while (
1426-
typeof value === 'object' &&
1427-
value !== null &&
1428-
value.$$typeof === REACT_LAZY_TYPE
1429-
) {
1446+
while (isLazy(value)) {
14301447
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
14311448
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
14321449
const referencedChunk: SomeChunk<any> = value._payload;
@@ -1475,7 +1492,7 @@ function fulfillReference(
14751492
const element: any = handler.value;
14761493
switch (key) {
14771494
case '3':
1478-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1495+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14791496
element.props = mappedValue;
14801497
break;
14811498
case '4':
@@ -1491,11 +1508,11 @@ function fulfillReference(
14911508
}
14921509
break;
14931510
default:
1494-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1511+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14951512
break;
14961513
}
14971514
} else if (__DEV__ && !reference.isDebug) {
1498-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1515+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14991516
}
15001517

15011518
handler.deps--;
@@ -1817,49 +1834,59 @@ function loadServerReference<A: Iterable<any>, T>(
18171834
return (null: any);
18181835
}
18191836

1837+
function isReactElementOrArrayLike(
1838+
value: any,
1839+
// eslint-disable-next-line no-undef
1840+
): value is {_debugInfo: null | ReactDebugInfo, ...} {
1841+
return (
1842+
typeof value === 'object' &&
1843+
value !== null &&
1844+
(isArray(value) ||
1845+
typeof value[ASYNC_ITERATOR] === 'function' ||
1846+
value.$$typeof === REACT_ELEMENT_TYPE)
1847+
);
1848+
}
1849+
1850+
function isLazy(
1851+
value: any,
1852+
// eslint-disable-next-line no-undef
1853+
): implies value is LazyComponent<
1854+
React$Element<any>,
1855+
SomeChunk<React$Element<any>>,
1856+
> {
1857+
return (
1858+
typeof value === 'object' &&
1859+
value !== null &&
1860+
value.$$typeof === REACT_LAZY_TYPE
1861+
);
1862+
}
1863+
1864+
function resolveLazy(value: mixed): mixed {
1865+
while (isLazy(value)) {
1866+
const payload: SomeChunk<any> = value._payload;
1867+
if (payload.status === INITIALIZED) {
1868+
value = payload.value;
1869+
continue;
1870+
}
1871+
break;
1872+
}
1873+
1874+
return value;
1875+
}
1876+
18201877
function transferReferencedDebugInfo(
18211878
parentChunk: null | SomeChunk<any>,
18221879
referencedChunk: SomeChunk<any>,
1823-
referencedValue: mixed,
18241880
): void {
18251881
if (__DEV__) {
1826-
const referencedDebugInfo = referencedChunk._debugInfo;
1827-
// If we have a direct reference to an object that was rendered by a synchronous
1828-
// server component, it might have some debug info about how it was rendered.
1829-
// We forward this to the underlying object. This might be a React Element or
1830-
// an Array fragment.
1831-
// If this was a string / number return value we lose the debug info. We choose
1832-
// that tradeoff to allow sync server components to return plain values and not
1833-
// use them as React Nodes necessarily. We could otherwise wrap them in a Lazy.
1834-
if (
1835-
typeof referencedValue === 'object' &&
1836-
referencedValue !== null &&
1837-
(isArray(referencedValue) ||
1838-
typeof referencedValue[ASYNC_ITERATOR] === 'function' ||
1839-
referencedValue.$$typeof === REACT_ELEMENT_TYPE)
1840-
) {
1841-
// We should maybe use a unique symbol for arrays but this is a React owned array.
1842-
// $FlowFixMe[prop-missing]: This should be added to elements.
1843-
const existingDebugInfo: ?ReactDebugInfo =
1844-
(referencedValue._debugInfo: any);
1845-
if (existingDebugInfo == null) {
1846-
Object.defineProperty((referencedValue: any), '_debugInfo', {
1847-
configurable: false,
1848-
enumerable: false,
1849-
writable: true,
1850-
value: referencedDebugInfo.slice(0), // Clone so that pushing later isn't going into the original
1851-
});
1852-
} else {
1853-
// $FlowFixMe[method-unbinding]
1854-
existingDebugInfo.push.apply(existingDebugInfo, referencedDebugInfo);
1855-
}
1856-
}
1857-
// We also add the debug info to the initializing chunk since the resolution of that promise is
1858-
// also blocked by the referenced debug info. By adding it to both we can track it even if the array/element
1859-
// is extracted, or if the root is rendered as is.
1882+
// We add the debug info to the initializing chunk since the resolution of
1883+
// that promise is also blocked by the referenced debug info. By adding it
1884+
// to both we can track it even if the array/element/lazy is extracted, or
1885+
// if the root is rendered as is.
18601886
if (parentChunk !== null) {
1861-
const parentDebugInfo = parentChunk._debugInfo;
1862-
if (parentDebugInfo !== referencedDebugInfo) {
1887+
const referencedDebugInfo = referencedChunk._debugInfo;
1888+
if (referencedDebugInfo !== null) {
1889+
const parentDebugInfo = parentChunk._debugInfo;
18631890
for (let i = 0; i < referencedDebugInfo.length; ++i) {
18641891
const debugInfoEntry = referencedDebugInfo[i];
18651892
if (debugInfoEntry.name != null) {
@@ -1903,11 +1930,7 @@ function getOutlinedModel<T>(
19031930
case INITIALIZED:
19041931
let value = chunk.value;
19051932
for (let i = 1; i < path.length; i++) {
1906-
while (
1907-
typeof value === 'object' &&
1908-
value !== null &&
1909-
value.$$typeof === REACT_LAZY_TYPE
1910-
) {
1933+
while (isLazy(value)) {
19111934
const referencedChunk: SomeChunk<any> = value._payload;
19121935
switch (referencedChunk.status) {
19131936
case RESOLVED_MODEL:
@@ -1977,11 +2000,7 @@ function getOutlinedModel<T>(
19772000
value = value[path[i]];
19782001
}
19792002

1980-
while (
1981-
typeof value === 'object' &&
1982-
value !== null &&
1983-
value.$$typeof === REACT_LAZY_TYPE
1984-
) {
2003+
while (isLazy(value)) {
19852004
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
19862005
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
19872006
const referencedChunk: SomeChunk<any> = value._payload;
@@ -2010,7 +2029,7 @@ function getOutlinedModel<T>(
20102029
// If we're resolving the "owner" or "stack" slot of an Element array, we don't call
20112030
// transferReferencedDebugInfo because this reference is to a debug chunk.
20122031
} else {
2013-
transferReferencedDebugInfo(initializingChunk, chunk, chunkValue);
2032+
transferReferencedDebugInfo(initializingChunk, chunk);
20142033
}
20152034
return chunkValue;
20162035
case PENDING:
@@ -2468,7 +2487,6 @@ function parseModelString(
24682487
function parseModelTuple(
24692488
response: Response,
24702489
value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
2471-
isRoot: boolean,
24722490
): any {
24732491
const tuple: [mixed, mixed, mixed, mixed] = (value: any);
24742492

@@ -2477,7 +2495,6 @@ function parseModelTuple(
24772495
// Or even change the ReactElement type to be an array.
24782496
return createElement(
24792497
response,
2480-
isRoot,
24812498
tuple[1],
24822499
tuple[2],
24832500
tuple[3],
@@ -2727,18 +2744,34 @@ function resolveChunkDebugInfo(
27272744
chunk: SomeChunk<any>,
27282745
): void {
27292746
if (__DEV__ && enableAsyncDebugInfo) {
2730-
// Push the currently resolving chunk's debug info representing the stream
2731-
// on the Promise that was waiting on the stream.
2732-
const ioInfo = streamState._debugInfo;
2733-
const debugChunk = chunk._debugChunk;
2734-
if (debugChunk != null) {
2735-
// If there's a debug chunk, then we wait for it to resolve before adding
2736-
// the stream info as the last entry.
2737-
debugChunk.then(() => {
2738-
chunk._debugInfo.push({awaited: ioInfo});
2739-
});
2747+
// Add the currently resolving chunk's debug info representing the stream
2748+
// to the Promise that was waiting on the stream, or its underlying value.
2749+
const debugInfoEntry: ReactAsyncInfo = {awaited: streamState._debugInfo};
2750+
2751+
const addDebugInfo = () => {
2752+
const value = resolveLazy(chunk.value);
2753+
if (isReactElementOrArrayLike(value)) {
2754+
const debugInfo: ReactDebugInfo = [debugInfoEntry];
2755+
if (value._debugInfo) {
2756+
// $FlowFixMe[method-unbinding]
2757+
value._debugInfo.push.apply(value._debugInfo, debugInfo);
2758+
} else {
2759+
Object.defineProperty(value, '_debugInfo', {
2760+
configurable: false,
2761+
enumerable: false,
2762+
writable: true,
2763+
value: debugInfo,
2764+
});
2765+
}
2766+
} else if (chunk._debugInfo !== null) {
2767+
chunk._debugInfo.push(debugInfoEntry);
2768+
}
2769+
};
2770+
2771+
if (chunk.status === PENDING || chunk.status === BLOCKED) {
2772+
chunk.then(addDebugInfo, addDebugInfo);
27402773
} else {
2741-
chunk._debugInfo.push({awaited: ioInfo});
2774+
addDebugInfo();
27422775
}
27432776
}
27442777
}
@@ -5044,8 +5077,7 @@ function createFromJSONCallback(response: Response) {
50445077
return parseModelString(response, this, key, value);
50455078
}
50465079
if (typeof value === 'object' && value !== null) {
5047-
const isRoot = key === '';
5048-
return parseModelTuple(response, value, isRoot);
5080+
return parseModelTuple(response, value);
50495081
}
50505082
return value;
50515083
};

0 commit comments

Comments
 (0)