Skip to content

Commit 8dfb52d

Browse files
committed
Update on "[compiler] rfc: Include location information in identifiers and reactive scopes for debugging"
Summary: Using the change detection code to debug codebases that violate the rules of react is a lot easier when we have a source location corresponding to the value that has changed inappropriately. I didn't see an easy way to track that information in the existing data structures at the point of codegen, so this PR adds locations to identifiers and reactive scopes (the location of a reactive scope is the range of the locations of its included identifiers). I'm interested if there's a better way to do this that I missed! [ghstack-poisoned]
2 parents 819c414 + ca6f970 commit 8dfb52d

File tree

2 files changed

+35
-6
lines changed

2 files changed

+35
-6
lines changed

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
blank_issues_enabled: false
12
contact_links:
23
- name: 📃 Documentation Issue
34
url: https://github.com/reactjs/react.dev/issues/new/choose

packages/react-client/src/ReactFlightClient.js

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ export type Response = {
254254
_rowLength: number, // remaining bytes in the row. 0 indicates that we're looking for a newline.
255255
_buffer: Array<Uint8Array>, // chunks received so far as part of this row
256256
_tempRefs: void | TemporaryReferenceSet, // the set temporary references can be resolved from
257+
_debugRootTask?: null | ConsoleTask, // DEV-only
257258
};
258259

259260
function readChunk<T>(chunk: SomeChunk<T>): T {
@@ -614,6 +615,7 @@ function getTaskName(type: mixed): string {
614615
}
615616

616617
function createElement(
618+
response: Response,
617619
type: mixed,
618620
key: mixed,
619621
props: mixed,
@@ -697,9 +699,15 @@ function createElement(
697699
const callStack = buildFakeCallStack(stack, createTaskFn);
698700
// This owner should ideally have already been initialized to avoid getting
699701
// user stack frames on the stack.
700-
const ownerTask = owner === null ? null : initializeFakeTask(owner);
702+
const ownerTask =
703+
owner === null ? null : initializeFakeTask(response, owner);
701704
if (ownerTask === null) {
702-
task = callStack();
705+
const rootTask = response._debugRootTask;
706+
if (rootTask != null) {
707+
task = rootTask.run(callStack);
708+
} else {
709+
task = callStack();
710+
}
703711
} else {
704712
task = ownerTask.run(callStack);
705713
}
@@ -1106,6 +1114,7 @@ function parseModelTuple(
11061114
// TODO: Consider having React just directly accept these arrays as elements.
11071115
// Or even change the ReactElement type to be an array.
11081116
return createElement(
1117+
response,
11091118
tuple[1],
11101119
tuple[2],
11111120
tuple[3],
@@ -1149,6 +1158,14 @@ export function createResponse(
11491158
_buffer: [],
11501159
_tempRefs: temporaryReferences,
11511160
};
1161+
if (supportsCreateTask) {
1162+
// Any stacks that appear on the server need to be rooted somehow on the client
1163+
// so we create a root Task for this response which will be the root owner for any
1164+
// elements created by the server. We use the "use server" string to indicate that
1165+
// this is where we enter the server from the client.
1166+
// TODO: Make this string configurable.
1167+
response._debugRootTask = (console: any).createTask('"use server"');
1168+
}
11521169
// Don't inline this call because it causes closure to outline the call above.
11531170
response._fromJSON = createFromJSONCallback(response);
11541171
return response;
@@ -1730,6 +1747,7 @@ function buildFakeCallStack<T>(stack: string, innerCall: () => T): () => T {
17301747
}
17311748

17321749
function initializeFakeTask(
1750+
response: Response,
17331751
debugInfo: ReactComponentInfo | ReactAsyncInfo,
17341752
): null | ConsoleTask {
17351753
if (taskCache === null || typeof debugInfo.stack !== 'string') {
@@ -1745,7 +1763,7 @@ function initializeFakeTask(
17451763
const ownerTask =
17461764
componentInfo.owner == null
17471765
? null
1748-
: initializeFakeTask(componentInfo.owner);
1766+
: initializeFakeTask(response, componentInfo.owner);
17491767

17501768
// eslint-disable-next-line react-internal/no-production-logging
17511769
const createTaskFn = (console: any).createTask.bind(
@@ -1755,7 +1773,12 @@ function initializeFakeTask(
17551773
const callStack = buildFakeCallStack(stack, createTaskFn);
17561774

17571775
if (ownerTask === null) {
1758-
return callStack();
1776+
const rootTask = response._debugRootTask;
1777+
if (rootTask != null) {
1778+
return rootTask.run(callStack);
1779+
} else {
1780+
return callStack();
1781+
}
17591782
} else {
17601783
return ownerTask.run(callStack);
17611784
}
@@ -1776,7 +1799,7 @@ function resolveDebugInfo(
17761799
// We eagerly initialize the fake task because this resolving happens outside any
17771800
// render phase so we're not inside a user space stack at this point. If we waited
17781801
// to initialize it when we need it, we might be inside user code.
1779-
initializeFakeTask(debugInfo);
1802+
initializeFakeTask(response, debugInfo);
17801803
const chunk = getChunk(response, id);
17811804
const chunkDebugInfo: ReactDebugInfo =
17821805
chunk._debugInfo || (chunk._debugInfo = []);
@@ -1813,12 +1836,17 @@ function resolveConsoleEntry(
18131836
printToConsole.bind(null, methodName, args, env),
18141837
);
18151838
if (owner != null) {
1816-
const task = initializeFakeTask(owner);
1839+
const task = initializeFakeTask(response, owner);
18171840
if (task !== null) {
18181841
task.run(callStack);
18191842
return;
18201843
}
18211844
}
1845+
const rootTask = response._debugRootTask;
1846+
if (rootTask != null) {
1847+
rootTask.run(callStack);
1848+
return;
1849+
}
18221850
callStack();
18231851
}
18241852

0 commit comments

Comments
 (0)