Skip to content

Commit d976077

Browse files
committed
Transfer debug in wakeChunk
1 parent 80fbd1d commit d976077

File tree

4 files changed

+160
-128
lines changed

4 files changed

+160
-128
lines changed

packages/react-client/src/ReactFlightClient.js

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

517536
function rejectChunk(
@@ -1052,11 +1071,7 @@ function getTaskName(type: mixed): string {
10521071
// the client. There should only be one for any given owner chain.
10531072
return '"use client"';
10541073
}
1055-
if (
1056-
typeof type === 'object' &&
1057-
type !== null &&
1058-
type.$$typeof === REACT_LAZY_TYPE
1059-
) {
1074+
if (isLazy(type)) {
10601075
if (type._init === readChunk) {
10611076
// This is a lazy node created by Flight. It is probably a client reference.
10621077
// We use the "use client" string to indicate that this is the boundary into
@@ -1168,7 +1183,6 @@ function initializeElement(
11681183

11691184
function createElement(
11701185
response: Response,
1171-
isRoot: boolean,
11721186
type: mixed,
11731187
key: mixed,
11741188
props: mixed,
@@ -1277,19 +1291,10 @@ function createElement(
12771291
// a Lazy node referencing this Element to let everything around it proceed.
12781292
const blockedChunk: BlockedChunk<React$Element<any>> =
12791293
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-
}
12871294
handler.value = element;
12881295
handler.chunk = blockedChunk;
12891296
const lazyNode = createLazyChunkWrapper(blockedChunk, validated);
12901297
if (__DEV__) {
1291-
// Forward the live debug info of the lazy node to the element.
1292-
element._debugInfo = lazyNode._debugInfo;
12931298
// After we have initialized any blocked references, initialize stack etc.
12941299
const init = initializeElement.bind(null, response, element, lazyNode);
12951300
blockedChunk.then(init, init);
@@ -1346,11 +1351,7 @@ function fulfillReference(
13461351
const {response, handler, parentObject, key, map, path} = reference;
13471352

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

1425-
while (
1426-
typeof value === 'object' &&
1427-
value !== null &&
1428-
value.$$typeof === REACT_LAZY_TYPE
1429-
) {
1426+
while (isLazy(value)) {
14301427
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
14311428
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
14321429
const referencedChunk: SomeChunk<any> = value._payload;
@@ -1475,7 +1472,7 @@ function fulfillReference(
14751472
const element: any = handler.value;
14761473
switch (key) {
14771474
case '3':
1478-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1475+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14791476
element.props = mappedValue;
14801477
break;
14811478
case '4':
@@ -1491,11 +1488,11 @@ function fulfillReference(
14911488
}
14921489
break;
14931490
default:
1494-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1491+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14951492
break;
14961493
}
14971494
} else if (__DEV__ && !reference.isDebug) {
1498-
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1495+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk);
14991496
}
15001497

15011498
handler.deps--;
@@ -1817,49 +1814,63 @@ function loadServerReference<A: Iterable<any>, T>(
18171814
return (null: any);
18181815
}
18191816

1817+
function isReactElementOrArrayLike(
1818+
value: any,
1819+
// eslint-disable-next-line no-undef
1820+
): value is {_debugInfo: null | ReactDebugInfo, ...} {
1821+
return (
1822+
typeof value === 'object' &&
1823+
value !== null &&
1824+
(isArray(value) ||
1825+
typeof value[ASYNC_ITERATOR] === 'function' ||
1826+
value.$$typeof === REACT_ELEMENT_TYPE)
1827+
);
1828+
}
1829+
1830+
function isLazy(
1831+
value: any,
1832+
// eslint-disable-next-line no-undef
1833+
): value is {_debugInfo: null | ReactDebugInfo, ...} {
1834+
return (
1835+
typeof value === 'object' &&
1836+
value !== null &&
1837+
value.$$typeof === REACT_LAZY_TYPE
1838+
);
1839+
}
1840+
1841+
function resolveLazy(value: mixed): mixed {
1842+
while (isLazy(value)) {
1843+
const payload: SomeChunk<any> = value._payload;
1844+
if (payload.status === INITIALIZED) {
1845+
value = payload.value;
1846+
continue;
1847+
}
1848+
break;
1849+
}
1850+
1851+
return value;
1852+
}
1853+
18201854
function transferReferencedDebugInfo(
18211855
parentChunk: null | SomeChunk<any>,
18221856
referencedChunk: SomeChunk<any>,
1823-
referencedValue: mixed,
18241857
): void {
18251858
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.
1859+
// We add the debug info to the initializing chunk since the resolution of
1860+
// that promise is also blocked by the referenced debug info. By adding it
1861+
// to both we can track it even if the array/element/lazy is extracted, or
1862+
// if the root is rendered as is.
18601863
if (parentChunk !== null) {
1861-
const parentDebugInfo = parentChunk._debugInfo;
1862-
if (parentDebugInfo !== referencedDebugInfo) {
1864+
let referencedDebugInfo = referencedChunk._debugInfo;
1865+
if (referencedDebugInfo === null) {
1866+
const value = resolveLazy(referencedChunk.value);
1867+
if (isReactElementOrArrayLike(value)) {
1868+
// The debug info was already transferred from the chunk to the value.
1869+
referencedDebugInfo = value._debugInfo;
1870+
}
1871+
}
1872+
if (referencedDebugInfo !== null) {
1873+
const parentDebugInfo = parentChunk._debugInfo;
18631874
for (let i = 0; i < referencedDebugInfo.length; ++i) {
18641875
const debugInfoEntry = referencedDebugInfo[i];
18651876
if (debugInfoEntry.name != null) {
@@ -1903,11 +1914,7 @@ function getOutlinedModel<T>(
19031914
case INITIALIZED:
19041915
let value = chunk.value;
19051916
for (let i = 1; i < path.length; i++) {
1906-
while (
1907-
typeof value === 'object' &&
1908-
value !== null &&
1909-
value.$$typeof === REACT_LAZY_TYPE
1910-
) {
1917+
while (isLazy(value)) {
19111918
const referencedChunk: SomeChunk<any> = value._payload;
19121919
switch (referencedChunk.status) {
19131920
case RESOLVED_MODEL:
@@ -1977,11 +1984,7 @@ function getOutlinedModel<T>(
19771984
value = value[path[i]];
19781985
}
19791986

1980-
while (
1981-
typeof value === 'object' &&
1982-
value !== null &&
1983-
value.$$typeof === REACT_LAZY_TYPE
1984-
) {
1987+
while (isLazy(value)) {
19851988
// If what we're referencing is a Lazy it must be because we inserted one as a virtual node
19861989
// while it was blocked by other data. If it's no longer blocked, we can unwrap it.
19871990
const referencedChunk: SomeChunk<any> = value._payload;
@@ -2010,7 +2013,7 @@ function getOutlinedModel<T>(
20102013
// If we're resolving the "owner" or "stack" slot of an Element array, we don't call
20112014
// transferReferencedDebugInfo because this reference is to a debug chunk.
20122015
} else {
2013-
transferReferencedDebugInfo(initializingChunk, chunk, chunkValue);
2016+
transferReferencedDebugInfo(initializingChunk, chunk);
20142017
}
20152018
return chunkValue;
20162019
case PENDING:
@@ -2468,7 +2471,6 @@ function parseModelString(
24682471
function parseModelTuple(
24692472
response: Response,
24702473
value: {+[key: string]: JSONValue} | $ReadOnlyArray<JSONValue>,
2471-
isRoot: boolean,
24722474
): any {
24732475
const tuple: [mixed, mixed, mixed, mixed] = (value: any);
24742476

@@ -2477,7 +2479,6 @@ function parseModelTuple(
24772479
// Or even change the ReactElement type to be an array.
24782480
return createElement(
24792481
response,
2480-
isRoot,
24812482
tuple[1],
24822483
tuple[2],
24832484
tuple[3],
@@ -2727,18 +2728,34 @@ function resolveChunkDebugInfo(
27272728
chunk: SomeChunk<any>,
27282729
): void {
27292730
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-
});
2731+
// Add the currently resolving chunk's debug info representing the stream
2732+
// to the Promise that was waiting on the stream, or its underlying value.
2733+
const debugInfoEntry: ReactAsyncInfo = {awaited: streamState._debugInfo};
2734+
2735+
const addDebugInfo = () => {
2736+
const value = resolveLazy(chunk.value);
2737+
if (isReactElementOrArrayLike(value)) {
2738+
const debugInfo: ReactDebugInfo = [debugInfoEntry];
2739+
if (value._debugInfo) {
2740+
// $FlowFixMe[method-unbinding]
2741+
value._debugInfo.push.apply(value._debugInfo, debugInfo);
2742+
} else {
2743+
Object.defineProperty(value, '_debugInfo', {
2744+
configurable: false,
2745+
enumerable: false,
2746+
writable: true,
2747+
value: debugInfo,
2748+
});
2749+
}
2750+
} else if (chunk._debugInfo !== null) {
2751+
chunk._debugInfo.push(debugInfoEntry);
2752+
}
2753+
};
2754+
2755+
if (chunk.status === PENDING || chunk.status === BLOCKED) {
2756+
chunk.then(addDebugInfo, addDebugInfo);
27402757
} else {
2741-
chunk._debugInfo.push({awaited: ioInfo});
2758+
addDebugInfo();
27422759
}
27432760
}
27442761
}
@@ -4749,6 +4766,7 @@ export function processBinaryChunk(
47494766
streamState: StreamState,
47504767
chunk: Uint8Array,
47514768
): void {
4769+
// console.log(new TextDecoder().decode(chunk));
47524770
if (hasGCedResponse(weakResponse)) {
47534771
// Ignore more chunks if we've already GC:ed all listeners.
47544772
return;
@@ -5044,8 +5062,7 @@ function createFromJSONCallback(response: Response) {
50445062
return parseModelString(response, this, key, value);
50455063
}
50465064
if (typeof value === 'object' && value !== null) {
5047-
const isRoot = key === '';
5048-
return parseModelTuple(response, value, isRoot);
5065+
return parseModelTuple(response, value);
50495066
}
50505067
return value;
50515068
};

0 commit comments

Comments
 (0)