Skip to content

Commit

Permalink
[Flight ESM] Wire up Source Maps in the flight-esm fixture (#30758)
Browse files Browse the repository at this point in the history
Same as #29708 but for the flight-esm fixture.
  • Loading branch information
sebmarkbage authored Aug 22, 2024
1 parent 7a3fcc9 commit e483df4
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 8 deletions.
39 changes: 39 additions & 0 deletions fixtures/flight-esm/server/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,45 @@ app.use(
express.static('node_modules/react-server-dom-esm/esm')
);

if (process.env.NODE_ENV === 'development') {
app.get('/source-maps', async function (req, res, next) {
// Proxy the request to the regional server.
const proxiedHeaders = {
'X-Forwarded-Host': req.hostname,
'X-Forwarded-For': req.ips,
'X-Forwarded-Port': 3000,
'X-Forwarded-Proto': req.protocol,
};

const promiseForData = request(
{
host: '127.0.0.1',
port: 3001,
method: req.method,
path: req.originalUrl,
headers: proxiedHeaders,
},
req
);

try {
const rscResponse = await promiseForData;
res.set('Content-type', 'application/json');
rscResponse.on('data', data => {
res.write(data);
res.flush();
});
rscResponse.on('end', data => {
res.end();
});
} catch (e) {
console.error(`Failed to proxy request: ${e.stack}`);
res.statusCode = 500;
res.end();
}
});
}

app.listen(3000, () => {
console.log('Global Fizz/Webpack Server listening on port 3000...');
});
Expand Down
84 changes: 84 additions & 0 deletions fixtures/flight-esm/server/region.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const app = express();
const compress = require('compression');
const {Readable} = require('node:stream');

const nodeModule = require('node:module');

app.use(compress());

// Application
Expand Down Expand Up @@ -116,6 +118,88 @@ app.get('/todos', function (req, res) {
]);
});

if (process.env.NODE_ENV === 'development') {
const rootDir = path.resolve(__dirname, '../');

app.get('/source-maps', async function (req, res, next) {
try {
res.set('Content-type', 'application/json');
let requestedFilePath = req.query.name;

let isCompiledOutput = false;
if (requestedFilePath.startsWith('file://')) {
// We assume that if it was prefixed with file:// it's referring to the compiled output
// and if it's a direct file path we assume it's source mapped back to original format.
isCompiledOutput = true;
requestedFilePath = url.fileURLToPath(requestedFilePath);
}

const relativePath = path.relative(rootDir, requestedFilePath);
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
// This is outside the root directory of the app. Forbid it to be served.
res.status = 403;
res.write('{}');
res.end();
return;
}

const sourceMap = nodeModule.findSourceMap(requestedFilePath);
let map;
if (requestedFilePath.startsWith('node:')) {
// This is a node internal. We don't include any source code for this but we still
// generate a source map for it so that we can add it to an ignoreList automatically.
map = {
version: 3,
// We use the node:// protocol convention to teach Chrome DevTools that this is
// on a different protocol and not part of the current page.
sources: ['node:///' + requestedFilePath.slice(5)],
sourcesContent: ['// Node Internals'],
mappings: 'AAAA',
ignoreList: [0],
sourceRoot: '',
};
} else if (!sourceMap || !isCompiledOutput) {
// If a file doesn't have a source map, such as this file, then we generate a blank
// source map that just contains the original content and segments pointing to the
// original lines. If a line number points to uncompiled output, like if source mapping
// was already applied we also use this path.
const sourceContent = await readFile(requestedFilePath, 'utf8');
const lines = sourceContent.split('\n').length;
// We ensure to absolute
const sourceURL = url.pathToFileURL(requestedFilePath);
map = {
version: 3,
sources: [sourceURL],
sourcesContent: [sourceContent],
// Note: This approach to mapping each line only lets you jump to each line
// not jump to a column within a line. To do that, you need a proper source map
// generated for each parsed segment or add a segment for each column.
mappings: 'AAAA' + ';AACA'.repeat(lines - 1),
sourceRoot: '',
// Add any node_modules to the ignore list automatically.
ignoreList: requestedFilePath.includes('node_modules')
? [0]
: undefined,
};
} else {
// We always set prepareStackTrace before reading the stack so that we get the stack
// without source maps applied. Therefore we have to use the original source map.
// If something read .stack before we did, we might observe the line/column after
// source mapping back to the original file. We use the isCompiledOutput check above
// in that case.
map = sourceMap.payload;
}
res.write(JSON.stringify(map));
res.end();
} catch (x) {
res.status = 500;
res.write('{}');
res.end();
console.error(x);
}
});
}

app.listen(3001, () => {
console.log('Regional Flight Server listening on port 3001...');
});
Expand Down
11 changes: 11 additions & 0 deletions fixtures/flight-esm/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ import ReactDOM from 'react-dom/client';
import {createFromFetch, encodeReply} from 'react-server-dom-esm/client';

const moduleBaseURL = '/src/';

function findSourceMapURL(fileName) {
return (
document.location.origin +
'/source-maps?name=' +
encodeURIComponent(fileName)
);
}

let updateRoot;
async function callServer(id, args) {
const response = fetch('/', {
Expand All @@ -17,6 +26,7 @@ async function callServer(id, args) {
const {returnValue, root} = await createFromFetch(response, {
callServer,
moduleBaseURL,
findSourceMapURL,
});
// Refresh the tree with the new RSC payload.
startTransition(() => {
Expand All @@ -34,6 +44,7 @@ let data = createFromFetch(
{
callServer,
moduleBaseURL,
findSourceMapURL,
}
);

Expand Down
21 changes: 13 additions & 8 deletions fixtures/flight/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import {createFromFetch, encodeReply} from 'react-server-dom-webpack/client';
// TODO: This should be a dependency of the App but we haven't implemented CSS in Node yet.
import './style.css';

function findSourceMapURL(fileName) {
return (
document.location.origin +
'/source-maps?name=' +
encodeURIComponent(fileName)
);
}

let updateRoot;
async function callServer(id, args) {
const response = fetch('/', {
Expand All @@ -16,7 +24,10 @@ async function callServer(id, args) {
},
body: await encodeReply(args),
});
const {returnValue, root} = await createFromFetch(response, {callServer});
const {returnValue, root} = await createFromFetch(response, {
callServer,
findSourceMapURL,
});
// Refresh the tree with the new RSC payload.
startTransition(() => {
updateRoot(root);
Expand All @@ -39,13 +50,7 @@ async function hydrateApp() {
}),
{
callServer,
findSourceMapURL(fileName) {
return (
document.location.origin +
'/source-maps?name=' +
encodeURIComponent(fileName)
);
},
findSourceMapURL,
}
);

Expand Down

0 comments on commit e483df4

Please sign in to comment.