Skip to content

Commit 1fb7d6e

Browse files
committed
Include environment name both in the virtual URL and findSourceMapURL
This way you can use the environment to know where to look for the source map in case you have multiple server environments.
1 parent 933b737 commit 1fb7d6e

File tree

3 files changed

+90
-29
lines changed

3 files changed

+90
-29
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,10 @@ Chunk.prototype.then = function <T>(
232232
}
233233
};
234234

235-
export type FindSourceMapURLCallback = (fileName: string) => null | string;
235+
export type FindSourceMapURLCallback = (
236+
fileName: string,
237+
environmentName: string,
238+
) => null | string;
236239

237240
export type Response = {
238241
_bundlerConfig: SSRModuleMap,
@@ -689,7 +692,15 @@ function createElement(
689692
writable: true,
690693
value: null,
691694
});
695+
let env = '';
692696
if (enableOwnerStacks) {
697+
if (owner !== null && owner.env != null) {
698+
// Interestingly we don't actually have the environment name of where
699+
// this JSX was created if it doesn't have an owner but if it does
700+
// it must be the same environment as the owner. We could send it separately
701+
// but it seems a bit unnecessary for this edge case.
702+
env = owner.env;
703+
}
693704
let normalizedStackTrace: null | Error = null;
694705
if (stack !== null) {
695706
// We create a fake stack and then create an Error object inside of it.
@@ -698,7 +709,11 @@ function createElement(
698709
// source mapping information.
699710
// This can unfortunately happen within a user space callstack which will
700711
// remain on the stack.
701-
normalizedStackTrace = createFakeJSXCallStackInDEV(response, stack);
712+
normalizedStackTrace = createFakeJSXCallStackInDEV(
713+
response,
714+
stack,
715+
env,
716+
);
702717
}
703718
Object.defineProperty(element, '_debugStack', {
704719
configurable: false,
@@ -713,7 +728,12 @@ function createElement(
713728
console,
714729
getTaskName(type),
715730
);
716-
const callStack = buildFakeCallStack(response, stack, createTaskFn);
731+
const callStack = buildFakeCallStack(
732+
response,
733+
stack,
734+
env,
735+
createTaskFn,
736+
);
717737
// This owner should ideally have already been initialized to avoid getting
718738
// user stack frames on the stack.
719739
const ownerTask =
@@ -1821,6 +1841,7 @@ function resolveErrorDev(
18211841
const callStack = buildFakeCallStack(
18221842
response,
18231843
stack,
1844+
env,
18241845
// $FlowFixMe[incompatible-use]
18251846
Error.bind(
18261847
null,
@@ -1877,6 +1898,7 @@ function resolvePostponeDev(
18771898
id: number,
18781899
reason: string,
18791900
stack: ReactStackTrace,
1901+
env: string,
18801902
): void {
18811903
if (!__DEV__) {
18821904
// These errors should never make it into a build so we don't need to encode them in codes.json
@@ -1902,6 +1924,7 @@ function resolvePostponeDev(
19021924
const callStack = buildFakeCallStack(
19031925
response,
19041926
stack,
1927+
env,
19051928
// $FlowFixMe[incompatible-use]
19061929
Error.bind(null, reason || ''),
19071930
);
@@ -1946,6 +1969,7 @@ function createFakeFunction<T>(
19461969
sourceMap: null | string,
19471970
line: number,
19481971
col: number,
1972+
environmentName: string,
19491973
): FakeFunction<T> {
19501974
// This creates a fake copy of a Server Module. It represents a module that has already
19511975
// executed on the server but we re-execute a blank copy for its stack frames on the client.
@@ -1998,7 +2022,13 @@ function createFakeFunction<T>(
19982022
// 1) A printed stack trace string needs a unique URL to be able to source map it.
19992023
// 2) If source maps are disabled or fails, you should at least be able to tell
20002024
// which file it was.
2001-
code += '\n//# sourceURL=rsc://React/' + filename + '?' + fakeFunctionIdx++;
2025+
code +=
2026+
'\n//# sourceURL=rsc://React/' +
2027+
encodeURIComponent(environmentName) +
2028+
'/' +
2029+
filename +
2030+
'?' +
2031+
fakeFunctionIdx++;
20022032
code += '\n//# sourceMappingURL=' + sourceMap;
20032033
} else if (filename) {
20042034
code += '\n//# sourceURL=' + filename;
@@ -2022,19 +2052,28 @@ function createFakeFunction<T>(
20222052
function buildFakeCallStack<T>(
20232053
response: Response,
20242054
stack: ReactStackTrace,
2055+
environmentName: string,
20252056
innerCall: () => T,
20262057
): () => T {
20272058
let callStack = innerCall;
20282059
for (let i = 0; i < stack.length; i++) {
20292060
const frame = stack[i];
2030-
const frameKey = frame.join('-');
2061+
const frameKey = frame.join('-') + '-' + environmentName;
20312062
let fn = fakeFunctionCache.get(frameKey);
20322063
if (fn === undefined) {
20332064
const [name, filename, line, col] = frame;
2034-
const sourceMap = response._debugFindSourceMapURL
2035-
? response._debugFindSourceMapURL(filename)
2065+
const findSourceMapURL = response._debugFindSourceMapURL;
2066+
const sourceMap = findSourceMapURL
2067+
? findSourceMapURL(filename, environmentName)
20362068
: null;
2037-
fn = createFakeFunction(name, filename, sourceMap, line, col);
2069+
fn = createFakeFunction(
2070+
name,
2071+
filename,
2072+
sourceMap,
2073+
line,
2074+
col,
2075+
environmentName,
2076+
);
20382077
// TODO: This cache should technically live on the response since the _debugFindSourceMapURL
20392078
// function is an input and can vary by response.
20402079
fakeFunctionCache.set(frameKey, fn);
@@ -2064,7 +2103,7 @@ function initializeFakeTask(
20642103
}
20652104

20662105
const stack = debugInfo.stack;
2067-
2106+
const env = componentInfo.env == null ? '' : componentInfo.env;
20682107
const ownerTask =
20692108
componentInfo.owner == null
20702109
? null
@@ -2074,7 +2113,7 @@ function initializeFakeTask(
20742113
console,
20752114
getServerComponentTaskName(componentInfo),
20762115
);
2077-
const callStack = buildFakeCallStack(response, stack, createTaskFn);
2116+
const callStack = buildFakeCallStack(response, stack, env, createTaskFn);
20782117

20792118
let componentTask;
20802119
if (ownerTask === null) {
@@ -2096,10 +2135,12 @@ const createFakeJSXCallStack = {
20962135
'react-stack-bottom-frame': function (
20972136
response: Response,
20982137
stack: ReactStackTrace,
2138+
environmentName: string,
20992139
): Error {
21002140
const callStackForError = buildFakeCallStack(
21012141
response,
21022142
stack,
2143+
environmentName,
21032144
fakeJSXCallSite,
21042145
);
21052146
return callStackForError();
@@ -2109,6 +2150,7 @@ const createFakeJSXCallStack = {
21092150
const createFakeJSXCallStackInDEV: (
21102151
response: Response,
21112152
stack: ReactStackTrace,
2153+
environmentName: string,
21122154
) => Error = __DEV__
21132155
? // We use this technique to trick minifiers to preserve the function name.
21142156
(createFakeJSXCallStack['react-stack-bottom-frame'].bind(
@@ -2132,12 +2174,11 @@ function initializeFakeStack(
21322174
return;
21332175
}
21342176
if (debugInfo.stack != null) {
2177+
const stack = debugInfo.stack;
2178+
const env = debugInfo.env == null ? '' : debugInfo.env;
21352179
// $FlowFixMe[cannot-write]
21362180
// $FlowFixMe[prop-missing]
2137-
debugInfo.debugStack = createFakeJSXCallStackInDEV(
2138-
response,
2139-
debugInfo.stack,
2140-
);
2181+
debugInfo.debugStack = createFakeJSXCallStackInDEV(response, stack, env);
21412182
}
21422183
if (debugInfo.owner != null) {
21432184
// Initialize any owners not yet initialized.
@@ -2206,6 +2247,7 @@ function resolveConsoleEntry(
22062247
const callStack = buildFakeCallStack(
22072248
response,
22082249
stackTrace,
2250+
env,
22092251
printToConsole.bind(null, methodName, args, env),
22102252
);
22112253
if (owner != null) {
@@ -2445,6 +2487,7 @@ function processFullStringRow(
24452487
id,
24462488
postponeInfo.reason,
24472489
postponeInfo.stack,
2490+
postponeInfo.env,
24482491
);
24492492
} else {
24502493
resolvePostponeProd(response, id);

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

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,10 +1241,10 @@ describe('ReactFlight', () => {
12411241
const ClientErrorBoundary = clientReference(MyErrorBoundary);
12421242

12431243
function App() {
1244-
return (
1245-
<ClientErrorBoundary>
1246-
<ServerComponent />
1247-
</ClientErrorBoundary>
1244+
return ReactServer.createElement(
1245+
ClientErrorBoundary,
1246+
null,
1247+
ReactServer.createElement(ServerComponent),
12481248
);
12491249
}
12501250

@@ -1301,13 +1301,16 @@ describe('ReactFlight', () => {
13011301
],
13021302
findSourceMapURLCalls: gate(flags => flags.enableOwnerStacks)
13031303
? [
1304-
[__filename],
1305-
[__filename],
1304+
[__filename, 'Server'],
1305+
[__filename, 'Server'],
13061306
// TODO: What should we request here? The outer (<anonymous>) or the inner (inspected-page.html)?
1307-
['inspected-page.html:29:11), <anonymous>'],
1308-
['file://~/(some)(really)(exotic-directory)/ReactFlight-test.js'],
1309-
['file:///testing.js'],
1310-
[__filename],
1307+
['inspected-page.html:29:11), <anonymous>', 'Server'],
1308+
[
1309+
'file://~/(some)(really)(exotic-directory)/ReactFlight-test.js',
1310+
'Server',
1311+
],
1312+
['file:///testing.js', 'Server'],
1313+
[__filename, 'Server'],
13111314
]
13121315
: [],
13131316
});
@@ -2836,18 +2839,20 @@ describe('ReactFlight', () => {
28362839
); // The eval will end up normalizing these
28372840

28382841
let sawReactPrefix = false;
2842+
const environments = [];
28392843
await act(async () => {
28402844
ReactNoop.render(
28412845
<ErrorBoundary
28422846
expectedMessage="third-party-error"
28432847
expectedEnviromentName="third-party"
28442848
expectedErrorStack={expectedErrorStack}>
28452849
{ReactNoopFlightClient.read(transport, {
2846-
findSourceMapURL(url) {
2850+
findSourceMapURL(url, environmentName) {
28472851
if (url.startsWith('rsc://React/')) {
28482852
// We don't expect to see any React prefixed URLs here.
28492853
sawReactPrefix = true;
28502854
}
2855+
environments.push(environmentName);
28512856
// My not giving a source map, we should leave it intact.
28522857
return null;
28532858
},
@@ -2857,6 +2862,16 @@ describe('ReactFlight', () => {
28572862
});
28582863

28592864
expect(sawReactPrefix).toBe(false);
2865+
if (__DEV__ && gate(flags => flags.enableOwnerStacks)) {
2866+
expect(environments.slice(0, 4)).toEqual([
2867+
'Server',
2868+
'third-party',
2869+
'third-party',
2870+
'third-party',
2871+
]);
2872+
} else {
2873+
expect(environments).toEqual([]);
2874+
}
28602875
});
28612876

28622877
it('can change the environment name inside a component', async () => {

packages/react-server/src/ReactFlightServer.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,12 @@ function filterStackTrace(error: Error, skipFrames: number): ReactStackTrace {
155155
if (url.startsWith('rsc://React/')) {
156156
// This callsite is a virtual fake callsite that came from another Flight client.
157157
// We need to reverse it back into the original location by stripping its prefix
158-
// and suffix.
158+
// and suffix. We don't need the environment name because it's available on the
159+
// parent object that will contain the stack.
160+
const envIdx = url.indexOf('/', 12);
159161
const suffixIdx = url.lastIndexOf('?');
160-
if (suffixIdx > -1) {
161-
callsite[1] = url.slice(12, suffixIdx);
162+
if (envIdx > -1 && suffixIdx > -1) {
163+
callsite[1] = url.slice(envIdx + 1, suffixIdx);
162164
}
163165
}
164166
}
@@ -2857,14 +2859,15 @@ function emitPostponeChunk(
28572859
if (__DEV__) {
28582860
let reason = '';
28592861
let stack: ReactStackTrace;
2862+
const env = request.environmentName();
28602863
try {
28612864
// eslint-disable-next-line react-internal/safe-string-coercion
28622865
reason = String(postponeInstance.message);
28632866
stack = filterStackTrace(postponeInstance, 0);
28642867
} catch (x) {
28652868
stack = [];
28662869
}
2867-
row = serializeRowHeader('P', id) + stringify({reason, stack}) + '\n';
2870+
row = serializeRowHeader('P', id) + stringify({reason, stack, env}) + '\n';
28682871
} else {
28692872
// No reason included in prod.
28702873
row = serializeRowHeader('P', id) + '\n';

0 commit comments

Comments
 (0)