@samitouri / QOS-React-2 / commits / cfb8945f51

[Fizz] Implement debugInfo (#30174)

Stacked on #30170. 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.

Sebastian Markbåge committed Jul 2, 2024 at 18:26 UTC cfb8945f511add040e1d5427d9961337f98f7618
4 files changed +194 -80
packages/react-html/src/__tests__/ReactHTMLServer-test.js
+10 -8
@@ -244,17 +244,19 @@ if (!__EXPERIMENTAL__) {
244 expect(caughtErrors.length).toBe(1);
245 expect(caughtErrors[0].error).toBe(thrownError);
246 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 **)' +
250 - '\n in div (at **)' +
251 - '\n in div (at **)',
247 + __DEV__
248 + ? '\n in Baz (at **)' +
249 + '\n in div (at **)' +
250 + '\n in Bar (at **)' +
251 + '\n in Foo (at **)' +
252 + '\n in div (at **)'
253 + : '\n in Lazy (at **)' +
254 + '\n in div (at **)' +
255 + '\n in div (at **)',
256 );
257 expect(normalizeCodeLocInfo(caughtErrors[0].ownerStack)).toBe(
258 __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 **)'
259 + ? '\n in Bar (at **)' + '\n in Foo (at **)'
260 : null,
261 );
262 });
packages/react-reconciler/src/ReactFiberComponentStack.js
+4 -4
@@ -185,11 +185,11 @@ export function getOwnerStackByFiberInDev(
185 // another code path anyway. I.e. this is likely NOT a V8 based browser.
186 // This will cause some of the stack to have different formatting.
187 // 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;
192 }
191 - const componentInfo: ReactComponentInfo = (owner: any);
192 - owner = componentInfo.owner;
193 } else {
194 break;
195 }
packages/react-server/src/ReactFizzComponentStack.js
+24 -5
@@ -41,10 +41,19 @@ type ClassComponentStackNode = {
41 owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
42 stack?: null | string | Error, // DEV only
43 };
44 +type ServerComponentStackNode = {
45 + // DEV only
46 + tag: 3,
47 + parent: null | ComponentStackNode,
48 + type: string, // name + env
49 + owner?: null | ReactComponentInfo | ComponentStackNode, // DEV only
50 + stack?: null | string | Error, // DEV only
51 +};
52 export type ComponentStackNode =
53 | BuiltInComponentStackNode
54 | FunctionComponentStackNode
47 - | ClassComponentStackNode;
55 + | ClassComponentStackNode
56 + | ServerComponentStackNode;
57
58 export function getStackByComponentStackNode(
59 componentStack: ComponentStackNode,
@@ -63,6 +72,11 @@ export function getStackByComponentStackNode(
72 case 2:
73 info += describeClassComponentFrame(node.type);
74 break;
75 + case 3:
76 + if (__DEV__) {
77 + info += describeBuiltInComponentFrame(node.type);
78 + break;
79 + }
80 }
81 // $FlowFixMe[incompatible-type] we bail out when we get a null
82 node = node.parent;
@@ -110,6 +124,11 @@ export function getOwnerStackByComponentStackNodeInDev(
124 );
125 }
126 break;
127 + case 3:
128 + if (!componentStack.owner) {
129 + info += describeBuiltInComponentFrame(componentStack.type);
130 + }
131 + break;
132 }
133
134 let owner: void | null | ComponentStackNode | ReactComponentInfo =
@@ -137,11 +156,11 @@ export function getOwnerStackByComponentStackNodeInDev(
156 }
157 } else if (typeof owner.stack === 'string') {
158 // Server Component
140 - if (owner.stack !== '') {
141 - info += '\n' + owner.stack;
159 + const ownerStack: string = owner.stack;
160 + owner = owner.owner;
161 + if (owner && ownerStack !== '') {
162 + info += '\n' + ownerStack;
163 }
143 - const componentInfo: ReactComponentInfo = (owner: any);
144 - owner = componentInfo.owner;
164 } else {
165 break;
166 }
packages/react-server/src/ReactFizzServer.js
+156 -63
@@ -21,6 +21,7 @@ import type {
21 Thenable,
22 ReactFormState,
23 ReactComponentInfo,
24 + ReactDebugInfo,
25 } from 'shared/ReactTypes';
26 import type {LazyComponent as LazyComponentType} from 'react/src/ReactLazy';
27 import type {
@@ -886,6 +887,41 @@ function createClassComponentStack(
887 type,
888 };
889 }
890 +function createServerComponentStack(
891 + task: Task,
892 + debugInfo: void | null | ReactDebugInfo,
893 +): null | ComponentStackNode {
894 + // Build a Server Component parent stack from the debugInfo.
895 + if (__DEV__) {
896 + let node = task.componentStack;
897 + if (debugInfo != null) {
898 + const stack: ReactDebugInfo = debugInfo;
899 + for (let i = 0; i < stack.length; i++) {
900 + const componentInfo: ReactComponentInfo = (stack[i]: any);
901 + if (typeof componentInfo.name !== 'string') {
902 + continue;
903 + }
904 + let name = componentInfo.name;
905 + const env = componentInfo.env;
906 + if (env) {
907 + name += ' (' + env + ')';
908 + }
909 + node = {
910 + tag: 3,
911 + parent: node,
912 + type: name,
913 + owner: componentInfo.owner,
914 + stack: componentInfo.stack,
915 + };
916 + }
917 + }
918 + return node;
919 + }
920 + // eslint-disable-next-line react-internal/prod-error-codes
921 + throw new Error(
922 + 'createServerComponentStack should never be called in production. This is a bug in React.',
923 + );
924 +}
925
926 function createComponentStackFromType(
927 task: Task,
@@ -1982,6 +2018,7 @@ function renderLazyComponent(
2018 stack: null | Error, // DEV only
2019 ): void {
2020 const previousComponentStack = task.componentStack;
2021 + // TODO: Do we really need this stack frame? We don't on the client.
2022 task.componentStack = createBuiltInComponentStack(task, 'Lazy', owner, stack);
2023 let Component;
2024 if (__DEV__) {
@@ -2533,72 +2570,90 @@ function renderNodeDestructive(
2570 const owner = __DEV__ ? element._owner : null;
2571 const stack = __DEV__ && enableOwnerStacks ? element._debugStack : null;
2572
2573 + const previousComponentStack = task.componentStack;
2574 + if (__DEV__) {
2575 + task.componentStack = createServerComponentStack(
2576 + task,
2577 + element._debugInfo,
2578 + );
2579 + }
2580 +
2581 const name = getComponentNameFromType(type);
2582 const keyOrIndex =
2583 key == null ? (childIndex === -1 ? 0 : childIndex) : key;
2584 const keyPath = [task.keyPath, name, keyOrIndex];
2585 if (task.replay !== null) {
2541 - if (__DEV__ && enableOwnerStacks) {
2542 - const debugTask: null | ConsoleTask = element._debugTask;
2543 - if (debugTask) {
2544 - debugTask.run(
2545 - replayElement.bind(
2546 - null,
2547 - request,
2548 - task,
2549 - keyPath,
2550 - name,
2551 - keyOrIndex,
2552 - childIndex,
2553 - type,
2554 - props,
2555 - ref,
2556 - task.replay,
2557 - owner,
2558 - stack,
2559 - ),
2560 - );
2561 - return;
2562 - }
2586 + const debugTask: null | ConsoleTask =
2587 + __DEV__ && enableOwnerStacks ? element._debugTask : null;
2588 + if (debugTask) {
2589 + debugTask.run(
2590 + replayElement.bind(
2591 + null,
2592 + request,
2593 + task,
2594 + keyPath,
2595 + name,
2596 + keyOrIndex,
2597 + childIndex,
2598 + type,
2599 + props,
2600 + ref,
2601 + task.replay,
2602 + owner,
2603 + stack,
2604 + ),
2605 + );
2606 + } else {
2607 + replayElement(
2608 + request,
2609 + task,
2610 + keyPath,
2611 + name,
2612 + keyOrIndex,
2613 + childIndex,
2614 + type,
2615 + props,
2616 + ref,
2617 + task.replay,
2618 + owner,
2619 + stack,
2620 + );
2621 }
2564 - replayElement(
2565 - request,
2566 - task,
2567 - keyPath,
2568 - name,
2569 - keyOrIndex,
2570 - childIndex,
2571 - type,
2572 - props,
2573 - ref,
2574 - task.replay,
2575 - owner,
2576 - stack,
2577 - );
2622 // No matches found for this node. We assume it's already emitted in the
2623 // prelude and skip it during the replay.
2624 } else {
2625 // We're doing a plain render.
2582 - if (__DEV__ && enableOwnerStacks) {
2583 - const debugTask: null | ConsoleTask = element._debugTask;
2584 - if (debugTask) {
2585 - debugTask.run(
2586 - renderElement.bind(
2587 - null,
2588 - request,
2589 - task,
2590 - keyPath,
2591 - type,
2592 - props,
2593 - ref,
2594 - owner,
2595 - stack,
2596 - ),
2597 - );
2598 - return;
2599 - }
2626 + const debugTask: null | ConsoleTask =
2627 + __DEV__ && enableOwnerStacks ? element._debugTask : null;
2628 + if (debugTask) {
2629 + debugTask.run(
2630 + renderElement.bind(
2631 + null,
2632 + request,
2633 + task,
2634 + keyPath,
2635 + type,
2636 + props,
2637 + ref,
2638 + owner,
2639 + stack,
2640 + ),
2641 + );
2642 + } else {
2643 + renderElement(
2644 + request,
2645 + task,
2646 + keyPath,
2647 + type,
2648 + props,
2649 + ref,
2650 + owner,
2651 + stack,
2652 + );
2653 }
2601 - renderElement(request, task, keyPath, type, props, ref, owner, stack);
2654 + }
2655 + if (__DEV__) {
2656 + task.componentStack = previousComponentStack;
2657 }
2658 return;
2659 }
@@ -2608,14 +2663,23 @@ function renderNodeDestructive(
2663 'Render them conditionally so that they only appear on the client render.',
2664 );
2665 case REACT_LAZY_TYPE: {
2611 - const previousComponentStack = task.componentStack;
2612 - task.componentStack = createBuiltInComponentStack(
2613 - task,
2614 - 'Lazy',
2615 - null,
2616 - null,
2617 - );
2666 const lazyNode: LazyComponentType<any, any> = (node: any);
2667 + const previousComponentStack = task.componentStack;
2668 + if (__DEV__) {
2669 + task.componentStack = createServerComponentStack(
2670 + task,
2671 + lazyNode._debugInfo,
2672 + );
2673 + }
2674 + if (!__DEV__ || task.componentStack === previousComponentStack) {
2675 + // TODO: Do we really need this stack frame? We don't on the client.
2676 + task.componentStack = createBuiltInComponentStack(
2677 + task,
2678 + 'Lazy',
2679 + null,
2680 + null,
2681 + );
2682 + }
2683 let resolvedNode;
2684 if (__DEV__) {
2685 resolvedNode = callLazyInitInDEV(lazyNode);
@@ -2746,12 +2810,23 @@ function renderNodeDestructive(
2810 // Clear any previous thenable state that was created by the unwrapping.
2811 task.thenableState = null;
2812 const thenable: Thenable<ReactNodeList> = (maybeUsable: any);
2749 - return renderNodeDestructive(
2813 + const previousComponentStack = task.componentStack;
2814 + if (__DEV__) {
2815 + task.componentStack = createServerComponentStack(
2816 + task,
2817 + thenable._debugInfo,
2818 + );
2819 + }
2820 + const result = renderNodeDestructive(
2821 request,
2822 task,
2823 unwrapThenable(thenable),
2824 childIndex,
2825 );
2826 + if (__DEV__) {
2827 + task.componentStack = previousComponentStack;
2828 + }
2829 + return result;
2830 }
2831
2832 if (maybeUsable.$$typeof === REACT_CONTEXT_TYPE) {
@@ -2982,6 +3057,15 @@ function renderChildrenArray(
3057 childIndex: number,
3058 ): void {
3059 const prevKeyPath = task.keyPath;
3060 + const previousComponentStack = task.componentStack;
3061 + if (__DEV__) {
3062 + // We read debugInfo from task.node instead of children because it might have been an
3063 + // unwrapped iterable so we read from the original node.
3064 + task.componentStack = createServerComponentStack(
3065 + task,
3066 + (task.node: any)._debugInfo,
3067 + );
3068 + }
3069 if (childIndex !== -1) {
3070 task.keyPath = [task.keyPath, 'Fragment', childIndex];
3071 if (task.replay !== null) {
@@ -2993,6 +3077,9 @@ function renderChildrenArray(
3077 childIndex,
3078 );
3079 task.keyPath = prevKeyPath;
3080 + if (__DEV__) {
3081 + task.componentStack = previousComponentStack;
3082 + }
3083 return;
3084 }
3085 }
@@ -3023,6 +3110,9 @@ function renderChildrenArray(
3110 }
3111 task.treeContext = prevTreeContext;
3112 task.keyPath = prevKeyPath;
3113 + if (__DEV__) {
3114 + task.componentStack = previousComponentStack;
3115 + }
3116 return;
3117 }
3118 }
@@ -3042,6 +3132,9 @@ function renderChildrenArray(
3132 // only need to reset it to the previous value at the very end.
3133 task.treeContext = prevTreeContext;
3134 task.keyPath = prevKeyPath;
3135 + if (__DEV__) {
3136 + task.componentStack = previousComponentStack;
3137 + }
3138 }
3139
3140 function trackPostpone(