mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Flight] Transfer Debug Info from a synchronous Reference to another Chunk (#34229)
This commit is contained in:
parent
d73b6f1110
commit
253abc78a1
96
packages/react-client/src/ReactFlightClient.js
vendored
96
packages/react-client/src/ReactFlightClient.js
vendored
|
|
@ -496,13 +496,14 @@ function createErrorChunk<T>(
|
||||||
function wakeChunk<T>(
|
function wakeChunk<T>(
|
||||||
listeners: Array<InitializationReference | (T => mixed)>,
|
listeners: Array<InitializationReference | (T => mixed)>,
|
||||||
value: T,
|
value: T,
|
||||||
|
chunk: SomeChunk<T>,
|
||||||
): void {
|
): void {
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
for (let i = 0; i < listeners.length; i++) {
|
||||||
const listener = listeners[i];
|
const listener = listeners[i];
|
||||||
if (typeof listener === 'function') {
|
if (typeof listener === 'function') {
|
||||||
listener(value);
|
listener(value);
|
||||||
} else {
|
} else {
|
||||||
fulfillReference(listener, value);
|
fulfillReference(listener, value, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -555,7 +556,7 @@ function wakeChunkIfInitialized<T>(
|
||||||
): void {
|
): void {
|
||||||
switch (chunk.status) {
|
switch (chunk.status) {
|
||||||
case INITIALIZED:
|
case INITIALIZED:
|
||||||
wakeChunk(resolveListeners, chunk.value);
|
wakeChunk(resolveListeners, chunk.value, chunk);
|
||||||
break;
|
break;
|
||||||
case BLOCKED:
|
case BLOCKED:
|
||||||
// It is possible that we're blocked on our own chunk if it's a cycle.
|
// It is possible that we're blocked on our own chunk if it's a cycle.
|
||||||
|
|
@ -569,7 +570,7 @@ function wakeChunkIfInitialized<T>(
|
||||||
if (cyclicHandler !== null) {
|
if (cyclicHandler !== null) {
|
||||||
// This reference points back to this chunk. We can resolve the cycle by
|
// This reference points back to this chunk. We can resolve the cycle by
|
||||||
// using the value from that handler.
|
// using the value from that handler.
|
||||||
fulfillReference(reference, cyclicHandler.value);
|
fulfillReference(reference, cyclicHandler.value, chunk);
|
||||||
resolveListeners.splice(i, 1);
|
resolveListeners.splice(i, 1);
|
||||||
i--;
|
i--;
|
||||||
if (rejectListeners !== null) {
|
if (rejectListeners !== null) {
|
||||||
|
|
@ -637,7 +638,7 @@ function triggerErrorOnChunk<T>(
|
||||||
cyclicChunk.status = BLOCKED;
|
cyclicChunk.status = BLOCKED;
|
||||||
cyclicChunk.value = null;
|
cyclicChunk.value = null;
|
||||||
cyclicChunk.reason = null;
|
cyclicChunk.reason = null;
|
||||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
|
||||||
initializingChunk = cyclicChunk;
|
initializingChunk = cyclicChunk;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -919,7 +920,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
|
||||||
cyclicChunk.value = null;
|
cyclicChunk.value = null;
|
||||||
cyclicChunk.reason = null;
|
cyclicChunk.reason = null;
|
||||||
|
|
||||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
|
||||||
initializingChunk = cyclicChunk;
|
initializingChunk = cyclicChunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -938,7 +939,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
|
||||||
if (resolveListeners !== null) {
|
if (resolveListeners !== null) {
|
||||||
cyclicChunk.value = null;
|
cyclicChunk.value = null;
|
||||||
cyclicChunk.reason = null;
|
cyclicChunk.reason = null;
|
||||||
wakeChunk(resolveListeners, value);
|
wakeChunk(resolveListeners, value, cyclicChunk);
|
||||||
}
|
}
|
||||||
if (initializingHandler !== null) {
|
if (initializingHandler !== null) {
|
||||||
if (initializingHandler.errored) {
|
if (initializingHandler.errored) {
|
||||||
|
|
@ -961,7 +962,7 @@ function initializeModelChunk<T>(chunk: ResolvedModelChunk<T>): void {
|
||||||
erroredChunk.reason = error;
|
erroredChunk.reason = error;
|
||||||
} finally {
|
} finally {
|
||||||
initializingHandler = prevHandler;
|
initializingHandler = prevHandler;
|
||||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
|
||||||
initializingChunk = prevChunk;
|
initializingChunk = prevChunk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1298,6 +1299,7 @@ function getChunk(response: Response, id: number): SomeChunk<any> {
|
||||||
function fulfillReference(
|
function fulfillReference(
|
||||||
reference: InitializationReference,
|
reference: InitializationReference,
|
||||||
value: any,
|
value: any,
|
||||||
|
fulfilledChunk: SomeChunk<any>,
|
||||||
): void {
|
): void {
|
||||||
const {response, handler, parentObject, key, map, path} = reference;
|
const {response, handler, parentObject, key, map, path} = reference;
|
||||||
|
|
||||||
|
|
@ -1376,6 +1378,8 @@ function fulfillReference(
|
||||||
const mappedValue = map(response, value, parentObject, key);
|
const mappedValue = map(response, value, parentObject, key);
|
||||||
parentObject[key] = mappedValue;
|
parentObject[key] = mappedValue;
|
||||||
|
|
||||||
|
transferReferencedDebugInfo(handler.chunk, fulfilledChunk, mappedValue);
|
||||||
|
|
||||||
// If this is the root object for a model reference, where `handler.value`
|
// If this is the root object for a model reference, where `handler.value`
|
||||||
// is a stale `null`, the resolved value can be used directly.
|
// is a stale `null`, the resolved value can be used directly.
|
||||||
if (key === '' && handler.value === null) {
|
if (key === '' && handler.value === null) {
|
||||||
|
|
@ -1422,7 +1426,7 @@ function fulfillReference(
|
||||||
initializedChunk.value = handler.value;
|
initializedChunk.value = handler.value;
|
||||||
initializedChunk.reason = handler.reason; // Used by streaming chunks
|
initializedChunk.reason = handler.reason; // Used by streaming chunks
|
||||||
if (resolveListeners !== null) {
|
if (resolveListeners !== null) {
|
||||||
wakeChunk(resolveListeners, handler.value);
|
wakeChunk(resolveListeners, handler.value, initializedChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1669,7 +1673,7 @@ function loadServerReference<A: Iterable<any>, T>(
|
||||||
initializedChunk.status = INITIALIZED;
|
initializedChunk.status = INITIALIZED;
|
||||||
initializedChunk.value = handler.value;
|
initializedChunk.value = handler.value;
|
||||||
if (resolveListeners !== null) {
|
if (resolveListeners !== null) {
|
||||||
wakeChunk(resolveListeners, handler.value);
|
wakeChunk(resolveListeners, handler.value, initializedChunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1728,6 +1732,49 @@ function loadServerReference<A: Iterable<any>, T>(
|
||||||
return (null: any);
|
return (null: any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function transferReferencedDebugInfo(
|
||||||
|
parentChunk: null | SomeChunk<any>,
|
||||||
|
referencedChunk: SomeChunk<any>,
|
||||||
|
referencedValue: mixed,
|
||||||
|
): void {
|
||||||
|
if (__DEV__ && referencedChunk._debugInfo) {
|
||||||
|
const referencedDebugInfo = referencedChunk._debugInfo;
|
||||||
|
// If we have a direct reference to an object that was rendered by a synchronous
|
||||||
|
// server component, it might have some debug info about how it was rendered.
|
||||||
|
// We forward this to the underlying object. This might be a React Element or
|
||||||
|
// an Array fragment.
|
||||||
|
// If this was a string / number return value we lose the debug info. We choose
|
||||||
|
// that tradeoff to allow sync server components to return plain values and not
|
||||||
|
// use them as React Nodes necessarily. We could otherwise wrap them in a Lazy.
|
||||||
|
if (
|
||||||
|
typeof referencedValue === 'object' &&
|
||||||
|
referencedValue !== null &&
|
||||||
|
(isArray(referencedValue) ||
|
||||||
|
typeof referencedValue[ASYNC_ITERATOR] === 'function' ||
|
||||||
|
referencedValue.$$typeof === REACT_ELEMENT_TYPE) &&
|
||||||
|
!referencedValue._debugInfo
|
||||||
|
) {
|
||||||
|
// We should maybe use a unique symbol for arrays but this is a React owned array.
|
||||||
|
// $FlowFixMe[prop-missing]: This should be added to elements.
|
||||||
|
Object.defineProperty((referencedValue: any), '_debugInfo', {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
value: referencedDebugInfo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// We also add it to the initializing chunk since the resolution of that promise is
|
||||||
|
// also blocked by these. By adding it to both we can track it even if the array/element
|
||||||
|
// is extracted, or if the root is rendered as is.
|
||||||
|
if (parentChunk !== null) {
|
||||||
|
const parentDebugInfo =
|
||||||
|
parentChunk._debugInfo || (parentChunk._debugInfo = []);
|
||||||
|
// $FlowFixMe[method-unbinding]
|
||||||
|
parentDebugInfo.push.apply(parentDebugInfo, referencedDebugInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getOutlinedModel<T>(
|
function getOutlinedModel<T>(
|
||||||
response: Response,
|
response: Response,
|
||||||
reference: string,
|
reference: string,
|
||||||
|
|
@ -1825,32 +1872,7 @@ function getOutlinedModel<T>(
|
||||||
value = value[path[i]];
|
value = value[path[i]];
|
||||||
}
|
}
|
||||||
const chunkValue = map(response, value, parentObject, key);
|
const chunkValue = map(response, value, parentObject, key);
|
||||||
if (__DEV__ && chunk._debugInfo) {
|
transferReferencedDebugInfo(initializingChunk, chunk, chunkValue);
|
||||||
// If we have a direct reference to an object that was rendered by a synchronous
|
|
||||||
// server component, it might have some debug info about how it was rendered.
|
|
||||||
// We forward this to the underlying object. This might be a React Element or
|
|
||||||
// an Array fragment.
|
|
||||||
// If this was a string / number return value we lose the debug info. We choose
|
|
||||||
// that tradeoff to allow sync server components to return plain values and not
|
|
||||||
// use them as React Nodes necessarily. We could otherwise wrap them in a Lazy.
|
|
||||||
if (
|
|
||||||
typeof chunkValue === 'object' &&
|
|
||||||
chunkValue !== null &&
|
|
||||||
(isArray(chunkValue) ||
|
|
||||||
typeof chunkValue[ASYNC_ITERATOR] === 'function' ||
|
|
||||||
chunkValue.$$typeof === REACT_ELEMENT_TYPE) &&
|
|
||||||
!chunkValue._debugInfo
|
|
||||||
) {
|
|
||||||
// We should maybe use a unique symbol for arrays but this is a React owned array.
|
|
||||||
// $FlowFixMe[prop-missing]: This should be added to elements.
|
|
||||||
Object.defineProperty((chunkValue: any), '_debugInfo', {
|
|
||||||
configurable: false,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
value: chunk._debugInfo,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chunkValue;
|
return chunkValue;
|
||||||
case PENDING:
|
case PENDING:
|
||||||
case BLOCKED:
|
case BLOCKED:
|
||||||
|
|
@ -2621,7 +2643,7 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
|
||||||
cyclicChunk.status = BLOCKED;
|
cyclicChunk.status = BLOCKED;
|
||||||
cyclicChunk.value = null;
|
cyclicChunk.value = null;
|
||||||
cyclicChunk.reason = null;
|
cyclicChunk.reason = null;
|
||||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
if ((enableProfilerTimer && enableComponentPerformanceTrack) || __DEV__) {
|
||||||
initializingChunk = cyclicChunk;
|
initializingChunk = cyclicChunk;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
@ -2650,7 +2672,7 @@ function resolveStream<T: ReadableStream | $AsyncIterable<any, any, void>>(
|
||||||
resolvedChunk.value = stream;
|
resolvedChunk.value = stream;
|
||||||
resolvedChunk.reason = controller;
|
resolvedChunk.reason = controller;
|
||||||
if (resolveListeners !== null) {
|
if (resolveListeners !== null) {
|
||||||
wakeChunk(resolveListeners, chunk.value);
|
wakeChunk(resolveListeners, chunk.value, chunk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2957,6 +2957,19 @@ describe('ReactFlight', () => {
|
||||||
transport: expect.arrayContaining([]),
|
transport: expect.arrayContaining([]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
time: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: 'third-party',
|
||||||
|
key: null,
|
||||||
|
name: 'ThirdPartyAsyncIterableComponent',
|
||||||
|
props: {},
|
||||||
|
stack: ' in Object.<anonymous> (at **)',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
time: 16,
|
||||||
|
},
|
||||||
{time: 17},
|
{time: 17},
|
||||||
]
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
@ -2976,6 +2989,19 @@ describe('ReactFlight', () => {
|
||||||
children: {},
|
children: {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
time: 19,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
time: 19,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
env: 'third-party',
|
||||||
|
key: null,
|
||||||
|
name: 'ThirdPartyAsyncIterableComponent',
|
||||||
|
props: {},
|
||||||
|
stack: ' in Object.<anonymous> (at **)',
|
||||||
|
},
|
||||||
{time: 19},
|
{time: 19},
|
||||||
]
|
]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user