Skip to content

Commit ff92b4b

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 f9aaca6 commit ff92b4b

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
@@ -385,7 +385,7 @@ The possibility to add custom middleware to the server response chain.
385385

386386
Type: `string => string`
387387

388-
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.
388+
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.
389389

390390
#### `runInspectorProxy`
391391

packages/metro/package.json

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

packages/metro/src/Server.js

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const {codeFrameColumns} = require('@babel/code-frame');
6565
const MultipartResponse = require('./Server/MultipartResponse');
6666
const debug = require('debug')('Metro:Server');
6767
const fs = require('graceful-fs');
68+
const invariant = require('invariant');
69+
const jscSafeUrl = require('jsc-safe-url');
6870
const {
6971
Logger,
7072
Logger: {createActionStartEntry, createActionEndEntry, log},
@@ -474,14 +476,19 @@ class Server {
474476
);
475477
}
476478

479+
_rewriteAndNormalizeUrl(requestUrl: string): string {
480+
return jscSafeUrl.toNormalUrl(
481+
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
482+
);
483+
}
484+
477485
async _processRequest(
478486
req: IncomingMessage,
479487
res: ServerResponse,
480488
next: (?Error) => mixed,
481489
) {
482490
const originalUrl = req.url;
483-
req.url = this._config.server.rewriteRequestUrl(req.url);
484-
491+
req.url = this._rewriteAndNormalizeUrl(req.url);
485492
const urlObj = url.parse(req.url, true);
486493
const {host} = req.headers;
487494
debug(
@@ -1145,19 +1152,34 @@ class Server {
11451152
debug('Start symbolication');
11461153
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
11471154
const body = await req.rawBody;
1148-
const stack = JSON.parse(body).stack.map(frame => {
1149-
if (frame.file && frame.file.includes('://')) {
1155+
const parsedBody = JSON.parse(body);
1156+
1157+
const rewriteAndNormalizeStackFrame = <T>(
1158+
frame: T,
1159+
lineNumber: number,
1160+
): T => {
1161+
invariant(
1162+
frame != null && typeof frame === 'object',
1163+
'Bad stack frame at line %d, expected object, received: %s',
1164+
lineNumber,
1165+
typeof frame,
1166+
);
1167+
const frameFile = frame.file;
1168+
if (typeof frameFile === 'string' && frameFile.includes('://')) {
11501169
return {
11511170
...frame,
1152-
file: this._config.server.rewriteRequestUrl(frame.file),
1171+
file: this._rewriteAndNormalizeUrl(frameFile),
11531172
};
11541173
}
11551174
return frame;
1156-
});
1175+
};
1176+
1177+
const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
11571178
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
11581179
const urls = new Set();
11591180

11601181
stack.forEach(frame => {
1182+
// These urls have been rewritten and normalized above.
11611183
const sourceUrl = frame.file;
11621184
// Skip `/debuggerWorker.js` which does not need symbolication.
11631185
if (
@@ -1172,8 +1194,11 @@ class Server {
11721194

11731195
debug('Getting source maps for symbolication');
11741196
const sourceMaps = await Promise.all(
1175-
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
1176-
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
1197+
Array.from(urls.values()).map(normalizedUrl =>
1198+
this._explodedSourceMapForBundleOptions(
1199+
this._parseOptions(normalizedUrl),
1200+
),
1201+
),
11771202
);
11781203

11791204
debug('Performing fast symbolication');
@@ -1200,21 +1225,17 @@ class Server {
12001225
}
12011226
}
12021227

1203-
async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1204-
const options = parseOptionsFromUrl(
1205-
reqUrl,
1206-
new Set(this._config.resolver.platforms),
1207-
getBytecodeVersion(),
1208-
);
1209-
1228+
async _explodedSourceMapForBundleOptions(
1229+
bundleOptions: BundleOptions,
1230+
): Promise<ExplodedSourceMap> {
12101231
const {
12111232
entryFile,
12121233
graphOptions,
12131234
onProgress,
12141235
resolverOptions,
12151236
serializerOptions,
12161237
transformOptions,
1217-
} = splitBundleOptions(options);
1238+
} = splitBundleOptions(bundleOptions);
12181239

12191240
/**
12201241
* `entryFile` is relative to projectRoot, we need to use resolution function

0 commit comments

Comments
 (0)