Skip to content

Commit 253abc7

Browse files
authored
[Flight] Transfer Debug Info from a synchronous Reference to another Chunk (facebook#34229)
1 parent d73b6f1 commit 253abc7

File tree

2 files changed

+85
-37
lines changed

2 files changed

+85
-37
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -496,13 +496,14 @@ function createErrorChunk<T>(
496496
function wakeChunk<T>(
497497
listeners: Array<InitializationReference | (T => mixed)>,
498498
value: T,
499+
chunk: SomeChunk<T>,
499500
): void {
500501
for (let i = 0; i < listeners.length; i++) {
501502
const listener = listeners[i];
502503
if (typeof listener === 'function') {
503504
listener(value);
504505
} else {
505-
fulfillReference(listener, value);
506+
fulfillReference(listener, value, chunk);
506507
}
507508
}
508509
}
@@ -555,7 +556,7 @@ function wakeChunkIfInitialized<T>(
555556
): void {
556557
switch (chunk.status) {
557558
case INITIALIZED:
558-
wakeChunk(resolveListeners, chunk.value);
559+
wakeChunk(resolveListeners, chunk.value, chunk);
559560
break;
560561
case BLOCKED:
561562
// It is possible that we're blocked on our own chunk if it's a cycle.
@@ -569,7 +570,7 @@ function wakeChunkIfInitialized<T>(
569570
if (cyclicHandler !== null) {
570571
// This reference points back to this chunk. We can resolve the cycle by
571572
// using the value from that handler.
572-
fulfillReference(reference, cyclicHandler.value);
573+
fulfillReference(reference, cyclicHandler.value, chunk);
573574
resolveListeners.splice(i, 1);
574575
i--;
575576
if (rejectListeners !== null) {
@@ -637,7 +638,7 @@ function triggerErrorOnChunk<T>(
637638
cyclicChunk.status = BLOCKED;
638639
cyclicChunk.value = null;
639640
cyclicChunk.reason = null;
640-
if (enableProfilerTimer && enableComponentPerformanceTrack) {
641+
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
641642
initializingChunk = cyclicChunk;
642643
}
643644
try {
@@ -919,7 +920,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
919920
cyclicChunk.value = null;
920921
cyclicChunk.reason = null;
921922

922-
if (enableProfilerTimer && enableComponentPerformanceTrack) {
923+
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
923924
initializingChunk = cyclicChunk;
924925
}
925926

@@ -938,7 +939,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
938939
if (resolveListeners !== null) {
939940
cyclicChunk.value = null;
940941
cyclicChunk.reason = null;
941-
wakeChunk(resolveListeners, value);
942+
wakeChunk(resolveListeners, value, cyclicChunk);
942943
}
943944
if (initializingHandler !== null) {
944945
if (initializingHandler.errored) {
@@ -961,7 +962,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
961962
erroredChunk.reason = error;
962963
} finally {
963964
initializingHandler = prevHandler;
964-
if (enableProfilerTimer && enableComponentPerformanceTrack) {
965+
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
965966
initializingChunk = prevChunk;
966967
}
967968
}
@@ -1298,6 +1299,7 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
12981299
function fulfillReference(
12991300
reference: InitializationReference,
13001301
value: any,
1302+
fulfilledChunk: SomeChunk<any>,
13011303
): void {
13021304
const {response, handler, parentObject, key, map, path} = reference;
13031305

@@ -1376,6 +1378,8 @@ function fulfillReference(
13761378
const mappedValue = map(response, value, parentObject, key);
13771379
parentObject[key] = mappedValue;
13781380

1381+
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
1382+
13791383
// If this is the root object for a model reference, where `handler.value`
13801384
// is a stale `null`, the resolved value can be used directly.
13811385
if (key === '' && handler.value === null) {
@@ -1422,7 +1426,7 @@ function fulfillReference(
14221426
initializedChunk.value = handler.value;
14231427
initializedChunk.reason = handler.reason; // Used by streaming chunks
14241428
if (resolveListeners !== null) {
1425-
wakeChunk(resolveListeners, handler.value);
1429+
wakeChunk(resolveListeners, handler.value, initializedChunk);
14261430
}
14271431
}
14281432
}
@@ -1669,7 +1673,7 @@ function loadServerReference<A: Iterable<any>, T>(
16691673
initializedChunk.status = INITIALIZED;
16701674
initializedChunk.value = handler.value;
16711675
if (resolveListeners !== null) {
1672-
wakeChunk(resolveListeners, handler.value);
1676+
wakeChunk(resolveListeners, handler.value, initializedChunk);
16731677
}
16741678
}
16751679
}
@@ -1728,6 +1732,49 @@ function loadServerReference<A: Iterable<any>, T>(
17281732
return (null: any);
17291733
}
17301734

1735+
function transferReferencedDebugInfo(
1736+
parentChunk: null | SomeChunk<any>,
1737+
referencedChunk: SomeChunk<any>,
1738+
referencedValue: mixed,
1739+
): void {
1740+
if (__DEV__ && referencedChunk._debugInfo) {
1741+
const referencedDebugInfo = referencedChunk._debugInfo;
1742+
// If we have a direct reference to an object that was rendered by a synchronous
1743+
// server component, it might have some debug info about how it was rendered.
1744+
// We forward this to the underlying object. This might be a React Element or
1745+
// an Array fragment.
1746+
// If this was a string / number return value we lose the debug info. We choose
1747+
// that tradeoff to allow sync server components to return plain values and not
1748+
// use them as React Nodes necessarily. We could otherwise wrap them in a Lazy.
1749+
if (
1750+
typeof referencedValue === 'object' &&
1751+
referencedValue !== null &&
1752+
(isArray(referencedValue) ||
1753+
typeof referencedValue[ASYNC_ITERATOR] === 'function' ||
1754+
referencedValue.$$typeof === REACT_ELEMENT_TYPE) &&
1755+
!referencedValue._debugInfo
1756+
) {
1757+
// We should maybe use a unique symbol for arrays but this is a React owned array.
1758+
// $FlowFixMe[prop-missing]: This should be added to elements.
1759+
Object.defineProperty((referencedValue: any), '_debugInfo', {
1760+
configurable: false,
1761+
enumerable: false,
1762+
writable: true,
1763+
value: referencedDebugInfo,
1764+
});
1765+
}
1766+
// We also add it to the initializing chunk since the resolution of that promise is
1767+
// also blocked by these. By adding it to both we can track it even if the array/element
1768+
// is extracted, or if the root is rendered as is.
1769+
if (parentChunk !== null) {
1770+
const parentDebugInfo =
1771+
parentChunk._debugInfo || (parentChunk._debugInfo = []);
1772+
// $FlowFixMe[method-unbinding]
1773+
parentDebugInfo.push.apply(parentDebugInfo, referencedDebugInfo);
1774+
}
1775+
}
1776+
}
1777+
17311778
function getOutlinedModel<T>(
17321779
response: Response,
17331780
reference: string,
@@ -1825,32 +1872,7 @@ function getOutlinedModel<T>(
18251872
value = value[path[i]];
18261873
}
18271874
const chunkValue = map(response, value, parentObject, key);
1828-
if (__DEV__ && chunk._debugInfo) {
1829-
// If we have a direct reference to an object that was rendered by a synchronous
1830-
// server component, it might have some debug info about how it was rendered.
1831-
// We forward this to the underlying object. This might be a React Element or
1832-
// an Array fragment.
1833-
// If this was a string / number return value we lose the debug info. We choose
1834-
// that tradeoff to allow sync server components to return plain values and not
1835-
// use them as React Nodes necessarily. We could otherwise wrap them in a Lazy.
1836-
if (
1837-
typeof chunkValue === 'object' &&
1838-
chunkValue !== null &&
1839-
(isArray(chunkValue) ||
1840-
typeof chunkValue[ASYNC_ITERATOR] === 'function' ||
1841-
chunkValue.$$typeof === REACT_ELEMENT_TYPE) &&
1842-
!chunkValue._debugInfo
1843-
) {
1844-
// We should maybe use a unique symbol for arrays but this is a React owned array.
1845-
// $FlowFixMe[prop-missing]: This should be added to elements.
1846-
Object.defineProperty((chunkValue: any), '_debugInfo', {
1847-
configurable: false,
1848-
enumerable: false,
1849-
writable: true,
1850-
value: chunk._debugInfo,
1851-
});
1852-
}
1853-
}
1875+
transferReferencedDebugInfo(initializingChunk, chunk, chunkValue);
18541876
return chunkValue;
18551877
case PENDING:
18561878
case BLOCKED:
@@ -2621,7 +2643,7 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
26212643
cyclicChunk.status = BLOCKED;
26222644
cyclicChunk.value = null;
26232645
cyclicChunk.reason = null;
2624-
if (enableProfilerTimer && enableComponentPerformanceTrack) {
2646+
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
26252647
initializingChunk = cyclicChunk;
26262648
}
26272649
try {
@@ -2650,7 +2672,7 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
26502672
resolvedChunk.value = stream;
26512673
resolvedChunk.reason = controller;
26522674
if (resolveListeners !== null) {
2653-
wakeChunk(resolveListeners, chunk.value);
2675+
wakeChunk(resolveListeners, chunk.value, chunk);
26542676
}
26552677
}
26562678

packages/react-client/src/__tests__/ReactFlight-test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2957,6 +2957,19 @@ describe('ReactFlight', () => {
29572957
transport: expect.arrayContaining([]),
29582958
},
29592959
},
2960+
{
2961+
time: 16,
2962+
},
2963+
{
2964+
env: 'third-party',
2965+
key: null,
2966+
name: 'ThirdPartyAsyncIterableComponent',
2967+
props: {},
2968+
stack: ' in Object.<anonymous> (at **)',
2969+
},
2970+
{
2971+
time: 16,
2972+
},
29602973
{time: 17},
29612974
]
29622975
: undefined,
@@ -2976,6 +2989,19 @@ describe('ReactFlight', () => {
29762989
children: {},
29772990
},
29782991
},
2992+
{
2993+
time: 19,
2994+
},
2995+
{
2996+
time: 19,
2997+
},
2998+
{
2999+
env: 'third-party',
3000+
key: null,
3001+
name: 'ThirdPartyAsyncIterableComponent',
3002+
props: {},
3003+
stack: ' in Object.<anonymous> (at **)',
3004+
},
29793005
{time: 19},
29803006
]
29813007
: undefined,

0 commit comments

Comments
 (0)