diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 46e09b1cf0274..8cb6606912980 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -7,7 +7,12 @@ * @flow */ -import type {Thenable} from 'shared/ReactTypes'; +import type { + Thenable, + ReactDebugInfo, + ReactComponentInfo, + ReactAsyncInfo, +} from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; import type { @@ -76,9 +81,6 @@ const RESOLVED_MODULE = 'resolved_module'; const INITIALIZED = 'fulfilled'; const ERRORED = 'rejected'; -// Dev-only -type ReactDebugInfo = Array<{+name?: string, +env?: string}>; - type PendingChunk = { status: 'pending', value: null | Array<(T) => mixed>, @@ -1014,7 +1016,7 @@ function resolveHint( function resolveDebugInfo( response: Response, id: number, - debugInfo: {name: string}, + debugInfo: ReactComponentInfo | ReactAsyncInfo, ): void { if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json diff --git a/packages/react-reconciler/src/ReactChildFiber.js b/packages/react-reconciler/src/ReactChildFiber.js index 5f6013d14b49f..6c273faed4b0b 100644 --- a/packages/react-reconciler/src/ReactChildFiber.js +++ b/packages/react-reconciler/src/ReactChildFiber.js @@ -8,7 +8,12 @@ */ import type {ReactElement} from 'shared/ReactElementType'; -import type {ReactPortal, Thenable, ReactContext} from 'shared/ReactTypes'; +import type { + ReactPortal, + Thenable, + ReactContext, + ReactDebugInfo, +} from 'shared/ReactTypes'; import type {Fiber} from './ReactInternalTypes'; import type {Lanes} from './ReactFiberLane'; import type {ThenableState} from './ReactFiberThenable'; @@ -50,6 +55,25 @@ import {readContextDuringReconcilation} from './ReactFiberNewContext'; let thenableState: ThenableState | null = null; let thenableIndexCounter: number = 0; +function mergeDebugInfo( + outer: ReactDebugInfo | null, + inner: ReactDebugInfo | null | void, +): ReactDebugInfo | null { + if (!__DEV__) { + return null; + } + if (inner == null) { + return outer; + } else if (outer === null) { + return inner; + } else { + // If we have two debugInfo, we need to create a new one. This makes the array no longer + // live so we'll miss any future updates if we received more so ideally we should always + // do this after both have fully resolved/unsuspended. + return outer.concat(inner); + } +} + let didWarnAboutMaps; let didWarnAboutGenerators; let didWarnAboutStringRefs; @@ -387,16 +411,23 @@ function createChildReconciler( current: Fiber | null, textContent: string, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ) { if (current === null || current.tag !== HostText) { // Insert const created = createFiberFromText(textContent, returnFiber.mode, lanes); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, textContent); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -406,6 +437,7 @@ function createChildReconciler( current: Fiber | null, element: ReactElement, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const elementType = element.type; if (elementType === REACT_FRAGMENT_TYPE) { @@ -415,6 +447,7 @@ function createChildReconciler( element.props.children, lanes, element.key, + debugInfo, ); } if (current !== null) { @@ -439,6 +472,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -447,6 +481,9 @@ function createChildReconciler( const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, current, element); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } @@ -455,6 +492,7 @@ function createChildReconciler( current: Fiber | null, portal: ReactPortal, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { if ( current === null || @@ -465,11 +503,17 @@ function createChildReconciler( // Insert const created = createFiberFromPortal(portal, returnFiber.mode, lanes); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, portal.children || []); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -480,6 +524,7 @@ function createChildReconciler( fragment: Iterable, lanes: Lanes, key: null | string, + debugInfo: null | ReactDebugInfo, ): Fiber { if (current === null || current.tag !== Fragment) { // Insert @@ -490,11 +535,17 @@ function createChildReconciler( key, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { // Update const existing = useFiber(current, fragment); existing.return = returnFiber; + if (__DEV__) { + existing._debugInfo = debugInfo; + } return existing; } } @@ -503,6 +554,7 @@ function createChildReconciler( returnFiber: Fiber, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -517,6 +569,9 @@ function createChildReconciler( lanes, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } @@ -530,6 +585,9 @@ function createChildReconciler( ); created.ref = coerceRef(returnFiber, null, newChild); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + } return created; } case REACT_PORTAL_TYPE: { @@ -539,12 +597,20 @@ function createChildReconciler( lanes, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } case REACT_LAZY_TYPE: { const payload = newChild._payload; const init = newChild._init; - return createChild(returnFiber, init(payload), lanes); + return createChild( + returnFiber, + init(payload), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), // call merge after init + ); } } @@ -556,6 +622,9 @@ function createChildReconciler( null, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = mergeDebugInfo(debugInfo, newChild._debugInfo); + } return created; } @@ -564,7 +633,12 @@ function createChildReconciler( // Unwrap the inner value and recursively call this function again. if (typeof newChild.then === 'function') { const thenable: Thenable = (newChild: any); - return createChild(returnFiber, unwrapThenable(thenable), lanes); + return createChild( + returnFiber, + unwrapThenable(thenable), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } if (newChild.$$typeof === REACT_CONTEXT_TYPE) { @@ -573,6 +647,7 @@ function createChildReconciler( returnFiber, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -593,6 +668,7 @@ function createChildReconciler( oldFiber: Fiber | null, newChild: any, lanes: Lanes, + debugInfo: null | ReactDebugInfo, ): Fiber | null { // Update the fiber if the keys match, otherwise return null. const key = oldFiber !== null ? oldFiber.key : null; @@ -607,21 +683,39 @@ function createChildReconciler( if (key !== null) { return null; } - return updateTextNode(returnFiber, oldFiber, '' + newChild, lanes); + return updateTextNode( + returnFiber, + oldFiber, + '' + newChild, + lanes, + debugInfo, + ); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { - return updateElement(returnFiber, oldFiber, newChild, lanes); + return updateElement( + returnFiber, + oldFiber, + newChild, + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } else { return null; } } case REACT_PORTAL_TYPE: { if (newChild.key === key) { - return updatePortal(returnFiber, oldFiber, newChild, lanes); + return updatePortal( + returnFiber, + oldFiber, + newChild, + lanes, + debugInfo, + ); } else { return null; } @@ -629,7 +723,13 @@ function createChildReconciler( case REACT_LAZY_TYPE: { const payload = newChild._payload; const init = newChild._init; - return updateSlot(returnFiber, oldFiber, init(payload), lanes); + return updateSlot( + returnFiber, + oldFiber, + init(payload), + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } } @@ -638,7 +738,14 @@ function createChildReconciler( return null; } - return updateFragment(returnFiber, oldFiber, newChild, lanes, null); + return updateFragment( + returnFiber, + oldFiber, + newChild, + lanes, + null, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } // Usable node types @@ -651,6 +758,7 @@ function createChildReconciler( oldFiber, unwrapThenable(thenable), lanes, + debugInfo, ); } @@ -661,6 +769,7 @@ function createChildReconciler( oldFiber, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -682,6 +791,7 @@ function createChildReconciler( newIdx: number, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { if ( (typeof newChild === 'string' && newChild !== '') || @@ -690,7 +800,13 @@ function createChildReconciler( // Text nodes don't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. const matchedFiber = existingChildren.get(newIdx) || null; - return updateTextNode(returnFiber, matchedFiber, '' + newChild, lanes); + return updateTextNode( + returnFiber, + matchedFiber, + '' + newChild, + lanes, + debugInfo, + ); } if (typeof newChild === 'object' && newChild !== null) { @@ -700,14 +816,26 @@ function createChildReconciler( existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updateElement(returnFiber, matchedFiber, newChild, lanes); + return updateElement( + returnFiber, + matchedFiber, + newChild, + lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } case REACT_PORTAL_TYPE: { const matchedFiber = existingChildren.get( newChild.key === null ? newIdx : newChild.key, ) || null; - return updatePortal(returnFiber, matchedFiber, newChild, lanes); + return updatePortal( + returnFiber, + matchedFiber, + newChild, + lanes, + debugInfo, + ); } case REACT_LAZY_TYPE: const payload = newChild._payload; @@ -718,12 +846,20 @@ function createChildReconciler( newIdx, init(payload), lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } if (isArray(newChild) || getIteratorFn(newChild)) { const matchedFiber = existingChildren.get(newIdx) || null; - return updateFragment(returnFiber, matchedFiber, newChild, lanes, null); + return updateFragment( + returnFiber, + matchedFiber, + newChild, + lanes, + null, + mergeDebugInfo(debugInfo, newChild._debugInfo), + ); } // Usable node types @@ -737,6 +873,7 @@ function createChildReconciler( newIdx, unwrapThenable(thenable), lanes, + debugInfo, ); } @@ -748,6 +885,7 @@ function createChildReconciler( newIdx, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -818,6 +956,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildren: Array, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This algorithm can't optimize by searching from both ends since we // don't have backpointers on fibers. I'm trying to see how far we can get @@ -866,6 +1005,7 @@ function createChildReconciler( oldFiber, newChildren[newIdx], lanes, + debugInfo, ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's @@ -913,7 +1053,12 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { - const newFiber = createChild(returnFiber, newChildren[newIdx], lanes); + const newFiber = createChild( + returnFiber, + newChildren[newIdx], + lanes, + debugInfo, + ); if (newFiber === null) { continue; } @@ -944,6 +1089,7 @@ function createChildReconciler( newIdx, newChildren[newIdx], lanes, + debugInfo, ); if (newFiber !== null) { if (shouldTrackSideEffects) { @@ -985,6 +1131,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChildrenIterable: Iterable, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This is the same implementation as reconcileChildrenArray(), // but using the iterator instead. @@ -1068,7 +1215,13 @@ function createChildReconciler( } else { nextOldFiber = oldFiber.sibling; } - const newFiber = updateSlot(returnFiber, oldFiber, step.value, lanes); + const newFiber = updateSlot( + returnFiber, + oldFiber, + step.value, + lanes, + debugInfo, + ); if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need @@ -1115,7 +1268,7 @@ function createChildReconciler( // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; !step.done; newIdx++, step = newChildren.next()) { - const newFiber = createChild(returnFiber, step.value, lanes); + const newFiber = createChild(returnFiber, step.value, lanes, debugInfo); if (newFiber === null) { continue; } @@ -1146,6 +1299,7 @@ function createChildReconciler( newIdx, step.value, lanes, + debugInfo, ); if (newFiber !== null) { if (shouldTrackSideEffects) { @@ -1211,6 +1365,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, element: ReactElement, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const key = element.key; let child = currentFirstChild; @@ -1226,6 +1381,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -1251,6 +1407,7 @@ function createChildReconciler( existing.return = returnFiber; if (__DEV__) { existing._debugOwner = element._owner; + existing._debugInfo = debugInfo; } return existing; } @@ -1272,11 +1429,17 @@ function createChildReconciler( element.key, ); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } else { const created = createFiberFromElement(element, returnFiber.mode, lanes); created.ref = coerceRef(returnFiber, currentFirstChild, element); created.return = returnFiber; + if (__DEV__) { + created._debugInfo = debugInfo; + } return created; } } @@ -1286,6 +1449,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, portal: ReactPortal, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber { const key = portal.key; let child = currentFirstChild; @@ -1325,6 +1489,7 @@ function createChildReconciler( currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, + debugInfo: ReactDebugInfo | null, ): Fiber | null { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, @@ -1354,6 +1519,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ), ); case REACT_PORTAL_TYPE: @@ -1363,17 +1529,18 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + debugInfo, ), ); case REACT_LAZY_TYPE: const payload = newChild._payload; const init = newChild._init; - // TODO: This function is supposed to be non-recursive. - return reconcileChildFibers( + return reconcileChildFibersImpl( returnFiber, currentFirstChild, init(payload), lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1383,6 +1550,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1392,6 +1560,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + mergeDebugInfo(debugInfo, newChild._debugInfo), ); } @@ -1418,6 +1587,7 @@ function createChildReconciler( currentFirstChild, unwrapThenable(thenable), lanes, + mergeDebugInfo(debugInfo, thenable._debugInfo), ); } @@ -1428,6 +1598,7 @@ function createChildReconciler( currentFirstChild, readContextDuringReconcilation(returnFiber, context, lanes), lanes, + debugInfo, ); } @@ -1472,6 +1643,7 @@ function createChildReconciler( currentFirstChild, newChild, lanes, + null, // debugInfo ); thenableState = null; // Don't bother to reset `thenableIndexCounter` to 0 because it always gets diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 8a01d87b8ff7c..82a07c2bbcf24 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -201,7 +201,7 @@ function FiberNode( if (__DEV__) { // This isn't directly used but is handy for debugging internals: - + this._debugInfo = null; this._debugOwner = null; this._debugNeedsRemount = false; this._debugHookTypes = null; @@ -347,6 +347,7 @@ export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber { } if (__DEV__) { + workInProgress._debugInfo = current._debugInfo; workInProgress._debugNeedsRemount = current._debugNeedsRemount; switch (workInProgress.tag) { case IndeterminateComponent: @@ -911,6 +912,7 @@ export function assignFiberPropertiesInDEV( target.treeBaseDuration = source.treeBaseDuration; } + target._debugInfo = source._debugInfo; target._debugOwner = source._debugOwner; target._debugNeedsRemount = source._debugNeedsRemount; target._debugHookTypes = source._debugHookTypes; diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 8b93630396666..8fd639322e46b 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -3761,6 +3761,10 @@ function remountFiber( newWorkInProgress.return = oldWorkInProgress.return; newWorkInProgress.ref = oldWorkInProgress.ref; + if (__DEV__) { + newWorkInProgress._debugInfo = oldWorkInProgress._debugInfo; + } + // Replace the child/sibling pointers above it. if (oldWorkInProgress === returnFiber.child) { returnFiber.child = newWorkInProgress; diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js index f4ec8ab186a05..126bb02cfdd7e 100644 --- a/packages/react-reconciler/src/ReactInternalTypes.js +++ b/packages/react-reconciler/src/ReactInternalTypes.js @@ -15,6 +15,7 @@ import type { Usable, ReactFormState, Awaited, + ReactDebugInfo, } from 'shared/ReactTypes'; import type {WorkTag} from './ReactWorkTags'; import type {TypeOfMode} from './ReactTypeOfMode'; @@ -199,6 +200,7 @@ export type Fiber = { // to be the same as work in progress. // __DEV__ only + _debugInfo?: ReactDebugInfo | null, _debugOwner?: Fiber | null, _debugIsCurrentlyTiming?: boolean, _debugNeedsRemount?: boolean, diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index e0d26b2d30473..1d1ca9891135d 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -52,6 +52,9 @@ import type { PendingThenable, FulfilledThenable, RejectedThenable, + ReactDebugInfo, + ReactComponentInfo, + ReactAsyncInfo, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; @@ -107,9 +110,6 @@ import {SuspenseException, getSuspendedThenable} from './ReactFlightThenable'; initAsyncDebugInfo(); -// Dev-only -type ReactDebugInfo = Array<{+name?: string, +env?: string}>; - const ObjectPrototype = Object.prototype; type JSONValue = @@ -1731,7 +1731,7 @@ function emitModelChunk(request: Request, id: number, json: string): void { function emitDebugChunk( request: Request, id: number, - debugInfo: {+name?: string, +env?: string}, + debugInfo: ReactComponentInfo | ReactAsyncInfo, ): void { if (!__DEV__) { // These errors should never make it into a build so we don't need to encode them in codes.json diff --git a/packages/react/src/ReactLazy.js b/packages/react/src/ReactLazy.js index ece18edca40a3..15417c2e763d0 100644 --- a/packages/react/src/ReactLazy.js +++ b/packages/react/src/ReactLazy.js @@ -7,7 +7,7 @@ * @flow */ -import type {Wakeable, Thenable} from 'shared/ReactTypes'; +import type {Wakeable, Thenable, ReactDebugInfo} from 'shared/ReactTypes'; import {REACT_LAZY_TYPE} from 'shared/ReactSymbols'; @@ -46,7 +46,7 @@ export type LazyComponent = { $$typeof: symbol | number, _payload: P, _init: (payload: P) => T, - _debugInfo?: null | Array<{+name?: string, +env?: string}>, + _debugInfo?: null | ReactDebugInfo, }; function lazyInitializer(payload: Payload): T { diff --git a/packages/shared/ReactTypes.js b/packages/shared/ReactTypes.js index 1fe711b779bf3..107a6082f8e58 100644 --- a/packages/shared/ReactTypes.js +++ b/packages/shared/ReactTypes.js @@ -111,20 +111,24 @@ interface ThenableImpl { } interface UntrackedThenable extends ThenableImpl { status?: void; + _debugInfo?: null | ReactDebugInfo; } export interface PendingThenable extends ThenableImpl { status: 'pending'; + _debugInfo?: null | ReactDebugInfo; } export interface FulfilledThenable extends ThenableImpl { status: 'fulfilled'; value: T; + _debugInfo?: null | ReactDebugInfo; } export interface RejectedThenable extends ThenableImpl { status: 'rejected'; reason: mixed; + _debugInfo?: null | ReactDebugInfo; } export type Thenable = @@ -173,3 +177,16 @@ export type Awaited = T extends null | void : empty // the argument to `then` was not callable. : T // argument was not an object : T; // non-thenable + +export type ReactComponentInfo = { + +name?: string, + +env?: string, +}; + +export type ReactAsyncInfo = { + +started?: number, + +completed?: number, + +stack?: string, +}; + +export type ReactDebugInfo = Array;