Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flight ESM] Wire up Source Maps in the flight-esm fixture #30758

Merged
merged 1 commit into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading