Skip to content

Commit dd09f3c

Browse files
committed
Accept bundle and symbolication requests in JSC-safe format (//& in place of ?)
Summary: The first part of implementing react-native-community/discussions-and-proposals#646 to address facebook/react-native#36794. This allows Metro to respond to bundle and symbolication requests that use URLs with `//&` in place of `?` as a query delimiter. ``` **[Feature]**: Support URLs for both bundling and symbolication requests using `//&` instead of `?` as a query string delimiter ``` (Note: This does *not* add support for registering HMR entry points in the JSC-safe format - that's not necessary at this point, if at all, and I'm keen to minimise the footprint of this stack for easier backporting.) Reviewed By: huntie Differential Revision: D45983877 fbshipit-source-id: e799f76cd26c2ca8026b4d1bf70a582814ae1790
1 parent f9020e1 commit dd09f3c

File tree

6 files changed

+290
-240
lines changed

6 files changed

+290
-240
lines changed

docs/Configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ The possibility to add custom middleware to the server response chain.
517517

518518
Type: `string => string`
519519

520-
A function that will be called every time Metro processes a URL. Metro will use the return value of this function as if it were the original URL provided by the client. This applies to all incoming HTTP requests (after any custom middleware), as well as bundle URLs in `/symbolicate` request payloads and within the hot reloading protocol.
520+
A function that will be called every time Metro processes a URL, after normalization of non-standard query-string delimiters using [`jsc-safe-url`](https://www.npmjs.com/package/jsc-safe-url). Metro will use the return value of this function as if it were the original URL provided by the client. This applies to all incoming HTTP requests (after any custom middleware), as well as bundle URLs in `/symbolicate` request payloads and within the hot reloading protocol.
521521

522522
#### `runInspectorProxy`
523523

packages/metro/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"image-size": "^0.6.0",
3535
"invariant": "^2.2.4",
3636
"jest-worker": "^27.2.0",
37+
"jsc-safe-url": "^0.2.2",
3738
"lodash.throttle": "^4.1.1",
3839
"metro-babel-transformer": "0.73.9",
3940
"metro-cache": "0.73.9",

packages/metro/src/Server.js

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ const {codeFrameColumns} = require('@babel/code-frame');
6767
const MultipartResponse = require('./Server/MultipartResponse');
6868
const debug = require('debug')('Metro:Server');
6969
const fs = require('graceful-fs');
70+
const invariant = require('invariant');
71+
const jscSafeUrl = require('jsc-safe-url');
7072
const {
7173
Logger,
7274
Logger: {createActionStartEntry, createActionEndEntry, log},
@@ -487,14 +489,19 @@ class Server {
487489
);
488490
}
489491

492+
_rewriteAndNormalizeUrl(requestUrl: string): string {
493+
return jscSafeUrl.toNormalUrl(
494+
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
495+
);
496+
}
497+
490498
async _processRequest(
491499
req: IncomingMessage,
492500
res: ServerResponse,
493501
next: (?Error) => mixed,
494502
) {
495503
const originalUrl = req.url;
496-
req.url = this._config.server.rewriteRequestUrl(req.url);
497-
504+
req.url = this._rewriteAndNormalizeUrl(req.url);
498505
const urlObj = url.parse(req.url, true);
499506
const {host} = req.headers;
500507
debug(
@@ -1223,19 +1230,34 @@ class Server {
12231230
debug('Start symbolication');
12241231
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
12251232
const body = await req.rawBody;
1226-
const stack = JSON.parse(body).stack.map(frame => {
1227-
if (frame.file && frame.file.includes('://')) {
1233+
const parsedBody = JSON.parse(body);
1234+
1235+
const rewriteAndNormalizeStackFrame = <T>(
1236+
frame: T,
1237+
lineNumber: number,
1238+
): T => {
1239+
invariant(
1240+
frame != null && typeof frame === 'object',
1241+
'Bad stack frame at line %d, expected object, received: %s',
1242+
lineNumber,
1243+
typeof frame,
1244+
);
1245+
const frameFile = frame.file;
1246+
if (typeof frameFile === 'string' && frameFile.includes('://')) {
12281247
return {
12291248
...frame,
1230-
file: this._config.server.rewriteRequestUrl(frame.file),
1249+
file: this._rewriteAndNormalizeUrl(frameFile),
12311250
};
12321251
}
12331252
return frame;
1234-
});
1253+
};
1254+
1255+
const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
12351256
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
12361257
const urls = new Set<string>();
12371258

12381259
stack.forEach(frame => {
1260+
// These urls have been rewritten and normalized above.
12391261
const sourceUrl = frame.file;
12401262
// Skip `/debuggerWorker.js` which does not need symbolication.
12411263
if (
@@ -1250,8 +1272,11 @@ class Server {
12501272

12511273
debug('Getting source maps for symbolication');
12521274
const sourceMaps = await Promise.all(
1253-
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
1254-
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
1275+
Array.from(urls.values()).map(normalizedUrl =>
1276+
this._explodedSourceMapForBundleOptions(
1277+
this._parseOptions(normalizedUrl),
1278+
),
1279+
),
12551280
);
12561281

12571282
debug('Performing fast symbolication');
@@ -1278,21 +1303,17 @@ class Server {
12781303
}
12791304
}
12801305

1281-
async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1282-
const options = parseOptionsFromUrl(
1283-
reqUrl,
1284-
new Set(this._config.resolver.platforms),
1285-
getBytecodeVersion(),
1286-
);
1287-
1306+
async _explodedSourceMapForBundleOptions(
1307+
bundleOptions: BundleOptions,
1308+
): Promise<ExplodedSourceMap> {
12881309
const {
12891310
entryFile,
12901311
graphOptions,
12911312
onProgress,
12921313
resolverOptions,
12931314
serializerOptions,
12941315
transformOptions,
1295-
} = splitBundleOptions(options);
1316+
} = splitBundleOptions(bundleOptions);
12961317

12971318
/**
12981319
* `entryFile` is relative to projectRoot, we need to use resolution function

0 commit comments

Comments
 (0)