Skip to content

Commit c2d1035

Browse files
authored
Configure the requested environment and annotate tasks at boundary between environments (#30455)
This enables configuring the name of the requested environment. When we currently use createTask, we start with a `"use server"` annotation. This option basically configures that string. I now also deal with the case when switching environments along the owner path. If you go from `"Third Party"` to `"Server"` to `"Client"`, it'll have a task named `"use third party"` at the root, then `"use server"` and then finally `"use client"`. We don't really have the concept of a Server Component making a request during render to then create another Server Component. Really the inner one should conceptually have the first one as its owner in that case. So currently the inner one will always have a null owner. We could somehow connect them in this server-to-server case. We don't currently have a way to configure the `"use client"` option but I figured maybe that could be inferred by the server environment that the Flight Client is executed within. Note: We did talk before about annotating each stack frame with the environment. You can effectively do that manually when parsing `rsc://React/{environment}/` from `captureOwnerStack`. However, we can't do that natively. At least not without deeper integration. Because it's the source map that's responsible for the actual function name of each stack frame - not what we give it at runtime. So for the native stacks, the task showing the change in environment is more practical.
1 parent e5d2245 commit c2d1035

File tree

10 files changed

+129
-39
lines changed

10 files changed

+129
-39
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 96 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ export type Response = {
255255
_debugRootTask?: null | ConsoleTask, // DEV-only
256256
_debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
257257
_replayConsole: boolean, // DEV-only
258+
_rootEnvironmentName: string, // DEV-only, the requested environment name.
258259
};
259260

260261
function readChunk<T>(chunk: SomeChunk<T>): T {
@@ -692,7 +693,7 @@ function createElement(
692693
writable: true,
693694
value: null,
694695
});
695-
let env = '';
696+
let env = response._rootEnvironmentName;
696697
if (enableOwnerStacks) {
697698
if (owner !== null && owner.env != null) {
698699
// Interestingly we don't actually have the environment name of where
@@ -737,7 +738,7 @@ function createElement(
737738
// This owner should ideally have already been initialized to avoid getting
738739
// user stack frames on the stack.
739740
const ownerTask =
740-
owner === null ? null : initializeFakeTask(response, owner);
741+
owner === null ? null : initializeFakeTask(response, owner, env);
741742
if (ownerTask === null) {
742743
const rootTask = response._debugRootTask;
743744
if (rootTask != null) {
@@ -1355,6 +1356,7 @@ function ResponseInstance(
13551356
temporaryReferences: void | TemporaryReferenceSet,
13561357
findSourceMapURL: void | FindSourceMapURLCallback,
13571358
replayConsole: boolean,
1359+
environmentName: void | string,
13581360
) {
13591361
const chunks: Map<number, SomeChunk<any>> = new Map();
13601362
this._bundlerConfig = bundlerConfig;
@@ -1371,17 +1373,21 @@ function ResponseInstance(
13711373
this._rowLength = 0;
13721374
this._buffer = [];
13731375
this._tempRefs = temporaryReferences;
1374-
if (supportsCreateTask) {
1375-
// Any stacks that appear on the server need to be rooted somehow on the client
1376-
// so we create a root Task for this response which will be the root owner for any
1377-
// elements created by the server. We use the "use server" string to indicate that
1378-
// this is where we enter the server from the client.
1379-
// TODO: Make this string configurable.
1380-
this._debugRootTask = (console: any).createTask('"use server"');
1381-
}
13821376
if (__DEV__) {
1377+
const rootEnv = environmentName === undefined ? 'Server' : environmentName;
1378+
if (supportsCreateTask) {
1379+
// Any stacks that appear on the server need to be rooted somehow on the client
1380+
// so we create a root Task for this response which will be the root owner for any
1381+
// elements created by the server. We use the "use server" string to indicate that
1382+
// this is where we enter the server from the client.
1383+
// TODO: Make this string configurable.
1384+
this._debugRootTask = (console: any).createTask(
1385+
'"use ' + rootEnv.toLowerCase() + '"',
1386+
);
1387+
}
13831388
this._debugFindSourceMapURL = findSourceMapURL;
13841389
this._replayConsole = replayConsole;
1390+
this._rootEnvironmentName = rootEnv;
13851391
}
13861392
// Don't inline this call because it causes closure to outline the call above.
13871393
this._fromJSON = createFromJSONCallback(this);
@@ -1396,6 +1402,7 @@ export function createResponse(
13961402
temporaryReferences: void | TemporaryReferenceSet,
13971403
findSourceMapURL: void | FindSourceMapURLCallback,
13981404
replayConsole: boolean,
1405+
environmentName: void | string,
13991406
): Response {
14001407
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
14011408
return new ResponseInstance(
@@ -1407,6 +1414,7 @@ export function createResponse(
14071414
temporaryReferences,
14081415
findSourceMapURL,
14091416
replayConsole,
1417+
environmentName,
14101418
);
14111419
}
14121420

@@ -1864,7 +1872,7 @@ function resolveErrorDev(
18641872
'An error occurred in the Server Components render but no message was provided',
18651873
),
18661874
);
1867-
const rootTask = response._debugRootTask;
1875+
const rootTask = getRootTask(response, env);
18681876
if (rootTask != null) {
18691877
error = rootTask.run(callStack);
18701878
} else {
@@ -2098,52 +2106,99 @@ function buildFakeCallStack<T>(
20982106
return callStack;
20992107
}
21002108

2109+
function getRootTask(
2110+
response: Response,
2111+
childEnvironmentName: string,
2112+
): null | ConsoleTask {
2113+
const rootTask = response._debugRootTask;
2114+
if (!rootTask) {
2115+
return null;
2116+
}
2117+
if (response._rootEnvironmentName !== childEnvironmentName) {
2118+
// If the root most owner component is itself in a different environment than the requested
2119+
// environment then we create an extra task to indicate that we're transitioning into it.
2120+
// Like if one environment just requests another environment.
2121+
const createTaskFn = (console: any).createTask.bind(
2122+
console,
2123+
'"use ' + childEnvironmentName.toLowerCase() + '"',
2124+
);
2125+
return rootTask.run(createTaskFn);
2126+
}
2127+
return rootTask;
2128+
}
2129+
21012130
function initializeFakeTask(
21022131
response: Response,
21032132
debugInfo: ReactComponentInfo | ReactAsyncInfo,
2133+
childEnvironmentName: string,
21042134
): null | ConsoleTask {
21052135
if (!supportsCreateTask) {
21062136
return null;
21072137
}
21082138
const componentInfo: ReactComponentInfo = (debugInfo: any); // Refined
2109-
const cachedEntry = componentInfo.debugTask;
2110-
if (cachedEntry !== undefined) {
2111-
return cachedEntry;
2112-
}
2113-
21142139
if (debugInfo.stack == null) {
21152140
// If this is an error, we should've really already initialized the task.
21162141
// If it's null, we can't initialize a task.
21172142
return null;
21182143
}
2119-
21202144
const stack = debugInfo.stack;
2121-
const env = componentInfo.env == null ? '' : componentInfo.env;
2122-
const ownerTask =
2123-
componentInfo.owner == null
2124-
? null
2125-
: initializeFakeTask(response, componentInfo.owner);
2126-
2127-
const createTaskFn = (console: any).createTask.bind(
2128-
console,
2129-
getServerComponentTaskName(componentInfo),
2130-
);
2131-
const callStack = buildFakeCallStack(response, stack, env, createTaskFn);
2145+
const env: string =
2146+
componentInfo.env == null
2147+
? response._rootEnvironmentName
2148+
: componentInfo.env;
2149+
if (env !== childEnvironmentName) {
2150+
// This is the boundary between two environments so we'll annotate the task name.
2151+
// That is unusual so we don't cache it.
2152+
const ownerTask =
2153+
componentInfo.owner == null
2154+
? null
2155+
: initializeFakeTask(response, componentInfo.owner, env);
2156+
return buildFakeTask(
2157+
response,
2158+
ownerTask,
2159+
stack,
2160+
'"use ' + childEnvironmentName.toLowerCase() + '"',
2161+
env,
2162+
);
2163+
} else {
2164+
const cachedEntry = componentInfo.debugTask;
2165+
if (cachedEntry !== undefined) {
2166+
return cachedEntry;
2167+
}
2168+
const ownerTask =
2169+
componentInfo.owner == null
2170+
? null
2171+
: initializeFakeTask(response, componentInfo.owner, env);
2172+
// $FlowFixMe[cannot-write]: We consider this part of initialization.
2173+
return (componentInfo.debugTask = buildFakeTask(
2174+
response,
2175+
ownerTask,
2176+
stack,
2177+
getServerComponentTaskName(componentInfo),
2178+
env,
2179+
));
2180+
}
2181+
}
21322182

2133-
let componentTask;
2183+
function buildFakeTask(
2184+
response: Response,
2185+
ownerTask: null | ConsoleTask,
2186+
stack: ReactStackTrace,
2187+
taskName: string,
2188+
env: string,
2189+
): ConsoleTask {
2190+
const createTaskFn = (console: any).createTask.bind(console, taskName);
2191+
const callStack = buildFakeCallStack(response, stack, env, createTaskFn);
21342192
if (ownerTask === null) {
2135-
const rootTask = response._debugRootTask;
2193+
const rootTask = getRootTask(response, env);
21362194
if (rootTask != null) {
2137-
componentTask = rootTask.run(callStack);
2195+
return rootTask.run(callStack);
21382196
} else {
2139-
componentTask = callStack();
2197+
return callStack();
21402198
}
21412199
} else {
2142-
componentTask = ownerTask.run(callStack);
2200+
return ownerTask.run(callStack);
21432201
}
2144-
// $FlowFixMe[cannot-write]: We consider this part of initialization.
2145-
componentInfo.debugTask = componentTask;
2146-
return componentTask;
21472202
}
21482203

21492204
const createFakeJSXCallStack = {
@@ -2216,7 +2271,9 @@ function resolveDebugInfo(
22162271
// We eagerly initialize the fake task because this resolving happens outside any
22172272
// render phase so we're not inside a user space stack at this point. If we waited
22182273
// to initialize it when we need it, we might be inside user code.
2219-
initializeFakeTask(response, debugInfo);
2274+
const env =
2275+
debugInfo.env === undefined ? response._rootEnvironmentName : debugInfo.env;
2276+
initializeFakeTask(response, debugInfo, env);
22202277
initializeFakeStack(response, debugInfo);
22212278

22222279
const chunk = getChunk(response, id);
@@ -2266,7 +2323,7 @@ function resolveConsoleEntry(
22662323
printToConsole.bind(null, methodName, args, env),
22672324
);
22682325
if (owner != null) {
2269-
const task = initializeFakeTask(response, owner);
2326+
const task = initializeFakeTask(response, owner, env);
22702327
initializeFakeStack(response, owner);
22712328
if (task !== null) {
22722329
task.run(callStack);
@@ -2275,7 +2332,7 @@ function resolveConsoleEntry(
22752332
// TODO: Set the current owner so that captureOwnerStack() adds the component
22762333
// stack during the replay - if needed.
22772334
}
2278-
const rootTask = response._debugRootTask;
2335+
const rootTask = getRootTask(response, env);
22792336
if (rootTask != null) {
22802337
rootTask.run(callStack);
22812338
return;

packages/react-noop-renderer/src/ReactNoopFlightClient.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ function read<T>(source: Source, options: ReadOptions): Thenable<T> {
6565
undefined,
6666
options !== undefined ? options.findSourceMapURL : undefined,
6767
true,
68+
undefined,
6869
);
6970
for (let i = 0; i < source.length; i++) {
7071
processBinaryChunk(response, source[i], 0);

packages/react-server-dom-esm/src/ReactFlightDOMClientBrowser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export type Options = {
4343
temporaryReferences?: TemporaryReferenceSet,
4444
findSourceMapURL?: FindSourceMapURLCallback,
4545
replayConsoleLogs?: boolean,
46+
environmentName?: string,
4647
};
4748

4849
function createResponseFromOptions(options: void | Options) {
@@ -59,6 +60,9 @@ function createResponseFromOptions(options: void | Options) {
5960
? options.findSourceMapURL
6061
: undefined,
6162
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
63+
__DEV__ && options && options.environmentName
64+
? options.environmentName
65+
: undefined,
6266
);
6367
}
6468

packages/react-server-dom-esm/src/ReactFlightDOMClientNode.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export type Options = {
5151
encodeFormAction?: EncodeFormActionCallback,
5252
findSourceMapURL?: FindSourceMapURLCallback,
5353
replayConsoleLogs?: boolean,
54+
environmentName?: string,
5455
};
5556

5657
function createFromNodeStream<T>(
@@ -70,6 +71,9 @@ function createFromNodeStream<T>(
7071
? options.findSourceMapURL
7172
: undefined,
7273
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
74+
__DEV__ && options && options.environmentName
75+
? options.environmentName
76+
: undefined,
7377
);
7478
stream.on('data', chunk => {
7579
processBinaryChunk(response, chunk);

packages/react-server-dom-turbopack/src/ReactFlightDOMClientBrowser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type Options = {
4242
temporaryReferences?: TemporaryReferenceSet,
4343
findSourceMapURL?: FindSourceMapURLCallback,
4444
replayConsoleLogs?: boolean,
45+
environmentName?: string,
4546
};
4647

4748
function createResponseFromOptions(options: void | Options) {
@@ -58,6 +59,9 @@ function createResponseFromOptions(options: void | Options) {
5859
? options.findSourceMapURL
5960
: undefined,
6061
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
62+
__DEV__ && options && options.environmentName
63+
? options.environmentName
64+
: undefined,
6165
);
6266
}
6367

packages/react-server-dom-turbopack/src/ReactFlightDOMClientEdge.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export type Options = {
7272
temporaryReferences?: TemporaryReferenceSet,
7373
findSourceMapURL?: FindSourceMapURLCallback,
7474
replayConsoleLogs?: boolean,
75+
environmentName?: string,
7576
};
7677

7778
function createResponseFromOptions(options: Options) {
@@ -88,6 +89,9 @@ function createResponseFromOptions(options: Options) {
8889
? options.findSourceMapURL
8990
: undefined,
9091
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
92+
__DEV__ && options && options.environmentName
93+
? options.environmentName
94+
: undefined,
9195
);
9296
}
9397

packages/react-server-dom-turbopack/src/ReactFlightDOMClientNode.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export type Options = {
6161
encodeFormAction?: EncodeFormActionCallback,
6262
findSourceMapURL?: FindSourceMapURLCallback,
6363
replayConsoleLogs?: boolean,
64+
environmentName?: string,
6465
};
6566

6667
function createFromNodeStream<T>(
@@ -79,6 +80,9 @@ function createFromNodeStream<T>(
7980
? options.findSourceMapURL
8081
: undefined,
8182
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
83+
__DEV__ && options && options.environmentName
84+
? options.environmentName
85+
: undefined,
8286
);
8387
stream.on('data', chunk => {
8488
processBinaryChunk(response, chunk);

packages/react-server-dom-webpack/src/ReactFlightDOMClientBrowser.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type Options = {
4242
temporaryReferences?: TemporaryReferenceSet,
4343
findSourceMapURL?: FindSourceMapURLCallback,
4444
replayConsoleLogs?: boolean,
45+
environmentName?: string,
4546
};
4647

4748
function createResponseFromOptions(options: void | Options) {
@@ -58,6 +59,9 @@ function createResponseFromOptions(options: void | Options) {
5859
? options.findSourceMapURL
5960
: undefined,
6061
__DEV__ ? (options ? options.replayConsoleLogs !== false : true) : false, // defaults to true
62+
__DEV__ && options && options.environmentName
63+
? options.environmentName
64+
: undefined,
6165
);
6266
}
6367

packages/react-server-dom-webpack/src/ReactFlightDOMClientEdge.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export type Options = {
7272
temporaryReferences?: TemporaryReferenceSet,
7373
findSourceMapURL?: FindSourceMapURLCallback,
7474
replayConsoleLogs?: boolean,
75+
environmentName?: string,
7576
};
7677

7778
function createResponseFromOptions(options: Options) {
@@ -88,6 +89,9 @@ function createResponseFromOptions(options: Options) {
8889
? options.findSourceMapURL
8990
: undefined,
9091
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
92+
__DEV__ && options && options.environmentName
93+
? options.environmentName
94+
: undefined,
9195
);
9296
}
9397

packages/react-server-dom-webpack/src/ReactFlightDOMClientNode.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export type Options = {
6262
encodeFormAction?: EncodeFormActionCallback,
6363
findSourceMapURL?: FindSourceMapURLCallback,
6464
replayConsoleLogs?: boolean,
65+
environmentName?: string,
6566
};
6667

6768
function createFromNodeStream<T>(
@@ -80,6 +81,9 @@ function createFromNodeStream<T>(
8081
? options.findSourceMapURL
8182
: undefined,
8283
__DEV__ && options ? options.replayConsoleLogs === true : false, // defaults to false
84+
__DEV__ && options && options.environmentName
85+
? options.environmentName
86+
: undefined,
8387
);
8488
stream.on('data', chunk => {
8589
if (typeof chunk === 'string') {

0 commit comments

Comments
 (0)