Skip to content

Commit a2da79b

Browse files
committed
Implement debugInfo in Fizz
This lets us track Server Component parent stacks in Fizz which also lets us track the correct owner stack for lazy. In Fiber we're careful not to make any DEV only fibers but since the ReactFizzComponentStack data structures just exist for debug meta data anyway we can just expand on that.
1 parent 57d5639 commit a2da79b

File tree

4 files changed

+171
-79
lines changed

4 files changed

+171
-79
lines changed

Diff for: packages/react-html/src/__tests__/ReactHTMLServer-test.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -244,17 +244,15 @@ if (!__EXPERIMENTAL__) {
244244
expect(caughtErrors.length).toBe(1);
245245
expect(caughtErrors[0].error).toBe(thrownError);
246246
expect(normalizeCodeLocInfo(caughtErrors[0].parentStack)).toBe(
247-
// TODO: Because Fizz doesn't yet implement debugInfo for parent stacks
248-
// it doesn't have the Server Components in the parent stacks.
249-
'\n in Lazy (at **)' +
247+
'\n in Baz (at **)' +
250248
'\n in div (at **)' +
249+
'\n in Bar (at **)' +
250+
'\n in Foo (at **)' +
251251
'\n in div (at **)',
252252
);
253253
expect(normalizeCodeLocInfo(caughtErrors[0].ownerStack)).toBe(
254254
__DEV__ && gate(flags => flags.enableOwnerStacks)
255-
? // TODO: Because Fizz doesn't yet implement debugInfo for parent stacks
256-
// it doesn't have the Server Components in the parent stacks.
257-
'\n in Lazy (at **)'
255+
? '\n in Bar (at **)' + '\n in Foo (at **)'
258256
: null,
259257
);
260258
});

Diff for: packages/react-reconciler/src/ReactFiberComponentStack.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ export function getOwnerStackByFiberInDev(
185185
// another code path anyway. I.e. this is likely NOT a V8 based browser.
186186
// This will cause some of the stack to have different formatting.
187187
// TODO: Normalize server component stacks to the client formatting.
188-
if (owner.stack !== '') {
189-
info += '\n' + owner.stack;
188+
const ownerStack: string = owner.stack;
189+
owner = owner.owner;
190+
if (owner && ownerStack !== '') {
191+
info += '\n' + ownerStack;
190192
}
191-
const componentInfo: ReactComponentInfo = (owner: any);
192-
owner = componentInfo.owner;
193193
} else {
194194
break;
195195
}

Diff for: packages/react-server/src/ReactFizzComponentStack.js

+7-6
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,14 @@ type ClassComponentStackNode = {
4141
owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
4242
stack?: null | string | Error, // DEV only
4343
};
44-
type ServerComponentStackNode = { // DEV only
44+
type ServerComponentStackNode = {
45+
// DEV only
4546
tag: 3,
4647
parent: null | ComponentStackNode,
4748
type: string, // name + env
4849
owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
4950
stack?: null | string | Error, // DEV only
50-
}
51+
};
5152
export type ComponentStackNode =
5253
| BuiltInComponentStackNode
5354
| FunctionComponentStackNode
@@ -153,11 +154,11 @@ export function getOwnerStackByComponentStackNodeInDev(
153154
}
154155
} else if (typeof owner.stack === 'string') {
155156
// Server Component
156-
if (owner.stack !== '') {
157-
info += '\n' + owner.stack;
157+
const ownerStack: string = owner.stack;
158+
owner = owner.owner;
159+
if (owner && ownerStack !== '') {
160+
info += '\n' + ownerStack;
158161
}
159-
const componentInfo: ReactComponentInfo = (owner: any);
160-
owner = componentInfo.owner;
161162
} else {
162163
break;
163164
}

Diff for: packages/react-server/src/ReactFizzServer.js

+156-63
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
Thenable,
2222
ReactFormState,
2323
ReactComponentInfo,
24+
ReactDebugInfo,
2425
} from 'shared/ReactTypes';
2526
import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
2627
import type {
@@ -882,6 +883,41 @@ function createClassComponentStack(
882883
type,
883884
};
884885
}
886+
function createServerComponentStack(
887+
task: Task,
888+
debugInfo: void | null | ReactDebugInfo,
889+
): null | ComponentStackNode {
890+
// Build a Server Component parent stack from the debugInfo.
891+
if (__DEV__) {
892+
let node = task.componentStack;
893+
if (debugInfo != null) {
894+
const stack: ReactDebugInfo = debugInfo;
895+
for (let i = 0; i < stack.length; i++) {
896+
const componentInfo: ReactComponentInfo = (stack[i]: any);
897+
if (typeof componentInfo.name !== 'string') {
898+
continue;
899+
}
900+
let name = componentInfo.name;
901+
const env = componentInfo.env;
902+
if (env) {
903+
name += ' (' + env + ')';
904+
}
905+
node = {
906+
tag: 3,
907+
parent: node,
908+
type: name,
909+
owner: componentInfo.owner,
910+
stack: componentInfo.stack,
911+
};
912+
}
913+
}
914+
return node;
915+
}
916+
// eslint-disable-next-line react-internal/prod-error-codes
917+
throw new Error(
918+
'createServerComponentStack should never be called in production. This is a bug in React.',
919+
);
920+
}
885921

886922
function createComponentStackFromType(
887923
task: Task,
@@ -1978,6 +2014,7 @@ function renderLazyComponent(
19782014
stack: null | Error, // DEV only
19792015
): void {
19802016
const previousComponentStack = task.componentStack;
2017+
// TODO: Do we really need this stack frame? We don't on the client.
19812018
task.componentStack = createBuiltInComponentStack(task, 'Lazy', owner, stack);
19822019
let Component;
19832020
if (__DEV__) {
@@ -2529,72 +2566,90 @@ function renderNodeDestructive(
25292566
const owner = __DEV__ ? element._owner : null;
25302567
const stack = __DEV__ && enableOwnerStacks ? element._debugStack : null;
25312568

2569+
const previousComponentStack = task.componentStack;
2570+
if (__DEV__) {
2571+
task.componentStack = createServerComponentStack(
2572+
task,
2573+
element._debugInfo,
2574+
);
2575+
}
2576+
25322577
const name = getComponentNameFromType(type);
25332578
const keyOrIndex =
25342579
key == null ? (childIndex === -1 ? 0 : childIndex) : key;
25352580
const keyPath = [task.keyPath, name, keyOrIndex];
25362581
if (task.replay !== null) {
2537-
if (__DEV__ && enableOwnerStacks) {
2538-
const debugTask: null | ConsoleTask = element._debugTask;
2539-
if (debugTask) {
2540-
debugTask.run(
2541-
replayElement.bind(
2542-
null,
2543-
request,
2544-
task,
2545-
keyPath,
2546-
name,
2547-
keyOrIndex,
2548-
childIndex,
2549-
type,
2550-
props,
2551-
ref,
2552-
task.replay,
2553-
owner,
2554-
stack,
2555-
),
2556-
);
2557-
return;
2558-
}
2582+
const debugTask: null | ConsoleTask =
2583+
__DEV__ && enableOwnerStacks ? element._debugTask : null;
2584+
if (debugTask) {
2585+
debugTask.run(
2586+
replayElement.bind(
2587+
null,
2588+
request,
2589+
task,
2590+
keyPath,
2591+
name,
2592+
keyOrIndex,
2593+
childIndex,
2594+
type,
2595+
props,
2596+
ref,
2597+
task.replay,
2598+
owner,
2599+
stack,
2600+
),
2601+
);
2602+
} else {
2603+
replayElement(
2604+
request,
2605+
task,
2606+
keyPath,
2607+
name,
2608+
keyOrIndex,
2609+
childIndex,
2610+
type,
2611+
props,
2612+
ref,
2613+
task.replay,
2614+
owner,
2615+
stack,
2616+
);
25592617
}
2560-
replayElement(
2561-
request,
2562-
task,
2563-
keyPath,
2564-
name,
2565-
keyOrIndex,
2566-
childIndex,
2567-
type,
2568-
props,
2569-
ref,
2570-
task.replay,
2571-
owner,
2572-
stack,
2573-
);
25742618
// No matches found for this node. We assume it's already emitted in the
25752619
// prelude and skip it during the replay.
25762620
} else {
25772621
// We're doing a plain render.
2578-
if (__DEV__ && enableOwnerStacks) {
2579-
const debugTask: null | ConsoleTask = element._debugTask;
2580-
if (debugTask) {
2581-
debugTask.run(
2582-
renderElement.bind(
2583-
null,
2584-
request,
2585-
task,
2586-
keyPath,
2587-
type,
2588-
props,
2589-
ref,
2590-
owner,
2591-
stack,
2592-
),
2593-
);
2594-
return;
2595-
}
2622+
const debugTask: null | ConsoleTask =
2623+
__DEV__ && enableOwnerStacks ? element._debugTask : null;
2624+
if (debugTask) {
2625+
debugTask.run(
2626+
renderElement.bind(
2627+
null,
2628+
request,
2629+
task,
2630+
keyPath,
2631+
type,
2632+
props,
2633+
ref,
2634+
owner,
2635+
stack,
2636+
),
2637+
);
2638+
} else {
2639+
renderElement(
2640+
request,
2641+
task,
2642+
keyPath,
2643+
type,
2644+
props,
2645+
ref,
2646+
owner,
2647+
stack,
2648+
);
25962649
}
2597-
renderElement(request, task, keyPath, type, props, ref, owner, stack);
2650+
}
2651+
if (__DEV__) {
2652+
task.componentStack = previousComponentStack;
25982653
}
25992654
return;
26002655
}
@@ -2604,14 +2659,23 @@ function renderNodeDestructive(
26042659
'Render them conditionally so that they only appear on the client render.',
26052660
);
26062661
case REACT_LAZY_TYPE: {
2607-
const previousComponentStack = task.componentStack;
2608-
task.componentStack = createBuiltInComponentStack(
2609-
task,
2610-
'Lazy',
2611-
null,
2612-
null,
2613-
);
26142662
const lazyNode: LazyComponentType<any, any> = (node: any);
2663+
const previousComponentStack = task.componentStack;
2664+
if (__DEV__) {
2665+
task.componentStack = createServerComponentStack(
2666+
task,
2667+
lazyNode._debugInfo,
2668+
);
2669+
}
2670+
if (!__DEV__ || task.componentStack === previousComponentStack) {
2671+
// TODO: Do we really need this stack frame? We don't on the client.
2672+
task.componentStack = createBuiltInComponentStack(
2673+
task,
2674+
'Lazy',
2675+
null,
2676+
null,
2677+
);
2678+
}
26152679
let resolvedNode;
26162680
if (__DEV__) {
26172681
resolvedNode = callLazyInitInDEV(lazyNode);
@@ -2742,12 +2806,23 @@ function renderNodeDestructive(
27422806
// Clear any previous thenable state that was created by the unwrapping.
27432807
task.thenableState = null;
27442808
const thenable: Thenable<ReactNodeList> = (maybeUsable: any);
2745-
return renderNodeDestructive(
2809+
const previousComponentStack = task.componentStack;
2810+
if (__DEV__) {
2811+
task.componentStack = createServerComponentStack(
2812+
task,
2813+
thenable._debugInfo,
2814+
);
2815+
}
2816+
const result = renderNodeDestructive(
27462817
request,
27472818
task,
27482819
unwrapThenable(thenable),
27492820
childIndex,
27502821
);
2822+
if (__DEV__) {
2823+
task.componentStack = previousComponentStack;
2824+
}
2825+
return result;
27512826
}
27522827

27532828
if (maybeUsable.$$typeof === REACT_CONTEXT_TYPE) {
@@ -2978,6 +3053,15 @@ function renderChildrenArray(
29783053
childIndex: number,
29793054
): void {
29803055
const prevKeyPath = task.keyPath;
3056+
const previousComponentStack = task.componentStack;
3057+
if (__DEV__) {
3058+
// We read debugInfo from task.node instead of children because it might have been an
3059+
// unwrapped iterable so we read from the original node.
3060+
task.componentStack = createServerComponentStack(
3061+
task,
3062+
(task.node: any)._debugInfo,
3063+
);
3064+
}
29813065
if (childIndex !== -1) {
29823066
task.keyPath = [task.keyPath, 'Fragment', childIndex];
29833067
if (task.replay !== null) {
@@ -2989,6 +3073,9 @@ function renderChildrenArray(
29893073
childIndex,
29903074
);
29913075
task.keyPath = prevKeyPath;
3076+
if (__DEV__) {
3077+
task.componentStack = previousComponentStack;
3078+
}
29923079
return;
29933080
}
29943081
}
@@ -3019,6 +3106,9 @@ function renderChildrenArray(
30193106
}
30203107
task.treeContext = prevTreeContext;
30213108
task.keyPath = prevKeyPath;
3109+
if (__DEV__) {
3110+
task.componentStack = previousComponentStack;
3111+
}
30223112
return;
30233113
}
30243114
}
@@ -3038,6 +3128,9 @@ function renderChildrenArray(
30383128
// only need to reset it to the previous value at the very end.
30393129
task.treeContext = prevTreeContext;
30403130
task.keyPath = prevKeyPath;
3131+
if (__DEV__) {
3132+
task.componentStack = previousComponentStack;
3133+
}
30413134
}
30423135

30433136
function trackPostpone(

0 commit comments

Comments
 (0)