Skip to content

Commit e483df4

Browse files
authored
[Flight ESM] Wire up Source Maps in the flight-esm fixture (#30758)
Same as #29708 but for the flight-esm fixture.
1 parent 7a3fcc9 commit e483df4

File tree

4 files changed

+147
-8
lines changed

4 files changed

+147
-8
lines changed

fixtures/flight-esm/server/global.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,45 @@ app.use(
131131
express.static('node_modules/react-server-dom-esm/esm')
132132
);
133133

134+
if (process.env.NODE_ENV === 'development') {
135+
app.get('/source-maps', async function (req, res, next) {
136+
// Proxy the request to the regional server.
137+
const proxiedHeaders = {
138+
'X-Forwarded-Host': req.hostname,
139+
'X-Forwarded-For': req.ips,
140+
'X-Forwarded-Port': 3000,
141+
'X-Forwarded-Proto': req.protocol,
142+
};
143+
144+
const promiseForData = request(
145+
{
146+
host: '127.0.0.1',
147+
port: 3001,
148+
method: req.method,
149+
path: req.originalUrl,
150+
headers: proxiedHeaders,
151+
},
152+
req
153+
);
154+
155+
try {
156+
const rscResponse = await promiseForData;
157+
res.set('Content-type', 'application/json');
158+
rscResponse.on('data', data => {
159+
res.write(data);
160+
res.flush();
161+
});
162+
rscResponse.on('end', data => {
163+
res.end();
164+
});
165+
} catch (e) {
166+
console.error(`Failed to proxy request: ${e.stack}`);
167+
res.statusCode = 500;
168+
res.end();
169+
}
170+
});
171+
}
172+
134173
app.listen(3000, () => {
135174
console.log('Global Fizz/Webpack Server listening on port 3000...');
136175
});

fixtures/flight-esm/server/region.js

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const app = express();
1717
const compress = require('compression');
1818
const {Readable} = require('node:stream');
1919

20+
const nodeModule = require('node:module');
21+
2022
app.use(compress());
2123

2224
// Application
@@ -116,6 +118,88 @@ app.get('/todos', function (req, res) {
116118
]);
117119
});
118120

121+
if (process.env.NODE_ENV === 'development') {
122+
const rootDir = path.resolve(__dirname, '../');
123+
124+
app.get('/source-maps', async function (req, res, next) {
125+
try {
126+
res.set('Content-type', 'application/json');
127+
let requestedFilePath = req.query.name;
128+
129+
let isCompiledOutput = false;
130+
if (requestedFilePath.startsWith('file://')) {
131+
// We assume that if it was prefixed with file:// it's referring to the compiled output
132+
// and if it's a direct file path we assume it's source mapped back to original format.
133+
isCompiledOutput = true;
134+
requestedFilePath = url.fileURLToPath(requestedFilePath);
135+
}
136+
137+
const relativePath = path.relative(rootDir, requestedFilePath);
138+
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
139+
// This is outside the root directory of the app. Forbid it to be served.
140+
res.status = 403;
141+
res.write('{}');
142+
res.end();
143+
return;
144+
}
145+
146+
const sourceMap = nodeModule.findSourceMap(requestedFilePath);
147+
let map;
148+
if (requestedFilePath.startsWith('node:')) {
149+
// This is a node internal. We don't include any source code for this but we still
150+
// generate a source map for it so that we can add it to an ignoreList automatically.
151+
map = {
152+
version: 3,
153+
// We use the node:// protocol convention to teach Chrome DevTools that this is
154+
// on a different protocol and not part of the current page.
155+
sources: ['node:///' + requestedFilePath.slice(5)],
156+
sourcesContent: ['// Node Internals'],
157+
mappings: 'AAAA',
158+
ignoreList: [0],
159+
sourceRoot: '',
160+
};
161+
} else if (!sourceMap || !isCompiledOutput) {
162+
// If a file doesn't have a source map, such as this file, then we generate a blank
163+
// source map that just contains the original content and segments pointing to the
164+
// original lines. If a line number points to uncompiled output, like if source mapping
165+
// was already applied we also use this path.
166+
const sourceContent = await readFile(requestedFilePath, 'utf8');
167+
const lines = sourceContent.split('\n').length;
168+
// We ensure to absolute
169+
const sourceURL = url.pathToFileURL(requestedFilePath);
170+
map = {
171+
version: 3,
172+
sources: [sourceURL],
173+
sourcesContent: [sourceContent],
174+
// Note: This approach to mapping each line only lets you jump to each line
175+
// not jump to a column within a line. To do that, you need a proper source map
176+
// generated for each parsed segment or add a segment for each column.
177+
mappings: 'AAAA' + ';AACA'.repeat(lines - 1),
178+
sourceRoot: '',
179+
// Add any node_modules to the ignore list automatically.
180+
ignoreList: requestedFilePath.includes('node_modules')
181+
? [0]
182+
: undefined,
183+
};
184+
} else {
185+
// We always set prepareStackTrace before reading the stack so that we get the stack
186+
// without source maps applied. Therefore we have to use the original source map.
187+
// If something read .stack before we did, we might observe the line/column after
188+
// source mapping back to the original file. We use the isCompiledOutput check above
189+
// in that case.
190+
map = sourceMap.payload;
191+
}
192+
res.write(JSON.stringify(map));
193+
res.end();
194+
} catch (x) {
195+
res.status = 500;
196+
res.write('{}');
197+
res.end();
198+
console.error(x);
199+
}
200+
});
201+
}
202+
119203
app.listen(3001, () => {
120204
console.log('Regional Flight Server listening on port 3001...');
121205
});

fixtures/flight-esm/src/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ import ReactDOM from 'react-dom/client';
44
import {createFromFetch, encodeReply} from 'react-server-dom-esm/client';
55

66
const moduleBaseURL = '/src/';
7+
8+
function findSourceMapURL(fileName) {
9+
return (
10+
document.location.origin +
11+
'/source-maps?name=' +
12+
encodeURIComponent(fileName)
13+
);
14+
}
15+
716
let updateRoot;
817
async function callServer(id, args) {
918
const response = fetch('/', {
@@ -17,6 +26,7 @@ async function callServer(id, args) {
1726
const {returnValue, root} = await createFromFetch(response, {
1827
callServer,
1928
moduleBaseURL,
29+
findSourceMapURL,
2030
});
2131
// Refresh the tree with the new RSC payload.
2232
startTransition(() => {
@@ -34,6 +44,7 @@ let data = createFromFetch(
3444
{
3545
callServer,
3646
moduleBaseURL,
47+
findSourceMapURL,
3748
}
3849
);
3950

fixtures/flight/src/index.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ import {createFromFetch, encodeReply} from 'react-server-dom-webpack/client';
66
// TODO: This should be a dependency of the App but we haven't implemented CSS in Node yet.
77
import './style.css';
88

9+
function findSourceMapURL(fileName) {
10+
return (
11+
document.location.origin +
12+
'/source-maps?name=' +
13+
encodeURIComponent(fileName)
14+
);
15+
}
16+
917
let updateRoot;
1018
async function callServer(id, args) {
1119
const response = fetch('/', {
@@ -16,7 +24,10 @@ async function callServer(id, args) {
1624
},
1725
body: await encodeReply(args),
1826
});
19-
const {returnValue, root} = await createFromFetch(response, {callServer});
27+
const {returnValue, root} = await createFromFetch(response, {
28+
callServer,
29+
findSourceMapURL,
30+
});
2031
// Refresh the tree with the new RSC payload.
2132
startTransition(() => {
2233
updateRoot(root);
@@ -39,13 +50,7 @@ async function hydrateApp() {
3950
}),
4051
{
4152
callServer,
42-
findSourceMapURL(fileName) {
43-
return (
44-
document.location.origin +
45-
'/source-maps?name=' +
46-
encodeURIComponent(fileName)
47-
);
48-
},
53+
findSourceMapURL,
4954
}
5055
);
5156

0 commit comments

Comments
 (0)