Skip to content

Commit 4a52348

Browse files
authored
Get Server Component Function Location for Parent Stacks using Child's Owner Stack (#33629)
This is using the same trick as #30798 but for runtime code too. It's essential zero cost. This lets us include a source location for parent stacks of Server Components when it has an owned child's location. Either from JSX or I/O. Ironically, a Component that throws an error will likely itself not get the stack because it won't have any JSX rendered yet.
1 parent 94cf60b commit 4a52348

File tree

8 files changed

+47
-9
lines changed

8 files changed

+47
-9
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,9 +2739,15 @@ function initializeFakeStack(
27392739
// $FlowFixMe[cannot-write]
27402740
debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env);
27412741
}
2742-
if (debugInfo.owner != null) {
2742+
const owner = debugInfo.owner;
2743+
if (owner != null) {
27432744
// Initialize any owners not yet initialized.
2744-
initializeFakeStack(response, debugInfo.owner);
2745+
initializeFakeStack(response, owner);
2746+
if (owner.debugLocation === undefined && debugInfo.debugStack != null) {
2747+
// If we are the child of this owner, then the owner should be the bottom frame
2748+
// our stack. We can use it as the implied location of the owner.
2749+
owner.debugLocation = debugInfo.debugStack;
2750+
}
27452751
}
27462752
}
27472753

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function getErrorForJestMatcher(error) {
6969

7070
function normalizeComponentInfo(debugInfo) {
7171
if (Array.isArray(debugInfo.stack)) {
72-
const {debugTask, debugStack, ...copy} = debugInfo;
72+
const {debugTask, debugStack, debugLocation, ...copy} = debugInfo;
7373
copy.stack = formatV8Stack(debugInfo.stack);
7474
if (debugInfo.owner) {
7575
copy.owner = normalizeComponentInfo(debugInfo.owner);

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5831,13 +5831,21 @@ export function attach(
58315831
}
58325832

58335833
function getSourceForInstance(instance: DevToolsInstance): Source | null {
5834-
const unresolvedSource = instance.source;
5834+
let unresolvedSource = instance.source;
58355835
if (unresolvedSource === null) {
58365836
// We don't have any source yet. We can try again later in case an owned child mounts later.
58375837
// TODO: We won't have any information here if the child is filtered.
58385838
return null;
58395839
}
58405840

5841+
if (instance.kind === VIRTUAL_INSTANCE) {
5842+
// We might have found one on the virtual instance.
5843+
const debugLocation = instance.data.debugLocation;
5844+
if (debugLocation != null) {
5845+
unresolvedSource = debugLocation;
5846+
}
5847+
}
5848+
58415849
// If we have the debug stack (the creation stack of the JSX) for any owned child of this
58425850
// component, then at the bottom of that stack will be a stack frame that is somewhere within
58435851
// the component's function body. Typically it would be the callsite of the JSX unless there's

packages/react-reconciler/src/ReactFiberComponentStack.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ export function getStackByFiberInDevAndProd(workInProgress: Fiber): string {
7979
for (let i = debugInfo.length - 1; i >= 0; i--) {
8080
const entry = debugInfo[i];
8181
if (typeof entry.name === 'string') {
82-
info += describeDebugInfoFrame(entry.name, entry.env);
82+
info += describeDebugInfoFrame(
83+
entry.name,
84+
entry.env,
85+
entry.debugLocation,
86+
);
8387
}
8488
}
8589
}

packages/react-server/src/ReactFizzComponentStack.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ function describeComponentStackByType(
8686
}
8787
}
8888
if (typeof type.name === 'string') {
89-
return describeDebugInfoFrame(type.name, type.env);
89+
return describeDebugInfoFrame(type.name, type.env, type.debugLocation);
9090
}
9191
}
9292
switch (type) {

packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function normalizeStack(stack) {
3737
}
3838

3939
function normalizeIOInfo(ioInfo) {
40-
const {debugTask, debugStack, ...copy} = ioInfo;
40+
const {debugTask, debugStack, debugLocation, ...copy} = ioInfo;
4141
if (ioInfo.stack) {
4242
copy.stack = normalizeStack(ioInfo.stack);
4343
}
@@ -72,7 +72,7 @@ function normalizeIOInfo(ioInfo) {
7272

7373
function normalizeDebugInfo(debugInfo) {
7474
if (Array.isArray(debugInfo.stack)) {
75-
const {debugTask, debugStack, ...copy} = debugInfo;
75+
const {debugTask, debugStack, debugLocation, ...copy} = debugInfo;
7676
copy.stack = normalizeStack(debugInfo.stack);
7777
if (debugInfo.owner) {
7878
copy.owner = normalizeDebugInfo(debugInfo.owner);

packages/shared/ReactComponentStackFrame.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
1313

1414
import DefaultPrepareStackTrace from 'shared/DefaultPrepareStackTrace';
1515

16+
import {formatOwnerStack} from './ReactOwnerStackFrames';
17+
1618
let prefix;
1719
let suffix;
1820
export function describeBuiltInComponentFrame(name: string): string {
@@ -38,7 +40,24 @@ export function describeBuiltInComponentFrame(name: string): string {
3840
return '\n' + prefix + name + suffix;
3941
}
4042

41-
export function describeDebugInfoFrame(name: string, env: ?string): string {
43+
export function describeDebugInfoFrame(
44+
name: string,
45+
env: ?string,
46+
location: ?Error,
47+
): string {
48+
if (location != null) {
49+
// If we have a location, it's the child's owner stack. Treat the bottom most frame as
50+
// the location of this function.
51+
const childStack = formatOwnerStack(location);
52+
const idx = childStack.lastIndexOf('\n');
53+
const lastLine = idx === -1 ? childStack : childStack.slice(idx + 1);
54+
if (lastLine.indexOf(name) !== -1) {
55+
// For async stacks it's possible we don't have the owner on it. As a precaution only
56+
// use this frame if it has the name of the function in it.
57+
return '\n' + lastLine;
58+
}
59+
}
60+
4261
return describeBuiltInComponentFrame(name + (env ? ' [' + env + ']' : ''));
4362
}
4463

packages/shared/ReactTypes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export type ReactComponentInfo = {
209209
// Stashed Data for the Specific Execution Environment. Not part of the transport protocol
210210
+debugStack?: null | Error,
211211
+debugTask?: null | ConsoleTask,
212+
debugLocation?: null | Error,
212213
};
213214

214215
export type ReactEnvironmentInfo = {

0 commit comments

Comments
 (0)