Skip to content

Commit 3b0a2b4

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 3c31ed5 commit 3b0a2b4

File tree

6 files changed

+288
-238
lines changed

6 files changed

+288
-238
lines changed

docs/Configuration.md

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

360360
Type: `string => string`
361361

362-
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.
362+
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.
363363

364364
#### `runInspectorProxy`
365365

packages/metro/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"invariant": "^2.2.4",
3737
"jest-haste-map": "^27.3.1",
3838
"jest-worker": "^27.2.0",
39+
"jsc-safe-url": "^0.2.2",
3940
"lodash.throttle": "^4.1.1",
4041
"metro-babel-transformer": "0.70.3",
4142
"metro-cache": "0.70.3",

packages/metro/src/Server.js

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const symbolicate = require('./Server/symbolicate');
6060
const {codeFrameColumns} = require('@babel/code-frame');
6161
const debug = require('debug')('Metro:Server');
6262
const fs = require('graceful-fs');
63+
const invariant = require('invariant');
64+
const jscSafeUrl = require('jsc-safe-url');
6365
const {
6466
Logger,
6567
Logger: {createActionStartEntry, createActionEndEntry, log},
@@ -444,14 +446,19 @@ class Server {
444446
);
445447
}
446448

449+
_rewriteAndNormalizeUrl(requestUrl: string): string {
450+
return jscSafeUrl.toNormalUrl(
451+
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
452+
);
453+
}
454+
447455
async _processRequest(
448456
req: IncomingMessage,
449457
res: ServerResponse,
450458
next: (?Error) => mixed,
451459
) {
452460
const originalUrl = req.url;
453-
req.url = this._config.server.rewriteRequestUrl(req.url);
454-
461+
req.url = this._rewriteAndNormalizeUrl(req.url);
455462
const urlObj = url.parse(req.url, true);
456463
const {host} = req.headers;
457464
debug(
@@ -1051,19 +1058,34 @@ class Server {
10511058
debug('Start symbolication');
10521059
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
10531060
const body = await req.rawBody;
1054-
const stack = JSON.parse(body).stack.map(frame => {
1055-
if (frame.file && frame.file.includes('://')) {
1061+
const parsedBody = JSON.parse(body);
1062+
1063+
const rewriteAndNormalizeStackFrame = <T>(
1064+
frame: T,
1065+
lineNumber: number,
1066+
): T => {
1067+
invariant(
1068+
frame != null && typeof frame === 'object',
1069+
'Bad stack frame at line %d, expected object, received: %s',
1070+
lineNumber,
1071+
typeof frame,
1072+
);
1073+
const frameFile = frame.file;
1074+
if (typeof frameFile === 'string' && frameFile.includes('://')) {
10561075
return {
10571076
...frame,
1058-
file: this._config.server.rewriteRequestUrl(frame.file),
1077+
file: this._rewriteAndNormalizeUrl(frameFile),
10591078
};
10601079
}
10611080
return frame;
1062-
});
1081+
};
1082+
1083+
const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
10631084
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
10641085
const urls = new Set();
10651086

10661087
stack.forEach(frame => {
1088+
// These urls have been rewritten and normalized above.
10671089
const sourceUrl = frame.file;
10681090
// Skip `/debuggerWorker.js` which does not need symbolication.
10691091
if (
@@ -1078,8 +1100,11 @@ class Server {
10781100

10791101
debug('Getting source maps for symbolication');
10801102
const sourceMaps = await Promise.all(
1081-
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
1082-
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
1103+
Array.from(urls.values()).map(normalizedUrl =>
1104+
this._explodedSourceMapForBundleOptions(
1105+
this._parseOptions(normalizedUrl),
1106+
),
1107+
),
10831108
);
10841109

10851110
debug('Performing fast symbolication');
@@ -1106,20 +1131,16 @@ class Server {
11061131
}
11071132
}
11081133

1109-
async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1110-
const options = parseOptionsFromUrl(
1111-
reqUrl,
1112-
new Set(this._config.resolver.platforms),
1113-
BYTECODE_VERSION,
1114-
);
1115-
1134+
async _explodedSourceMapForBundleOptions(
1135+
bundleOptions: BundleOptions,
1136+
): Promise<ExplodedSourceMap> {
11161137
const {
11171138
entryFile,
11181139
transformOptions,
11191140
serializerOptions,
11201141
graphOptions,
11211142
onProgress,
1122-
} = splitBundleOptions(options);
1143+
} = splitBundleOptions(bundleOptions);
11231144

11241145
/**
11251146
* `entryFile` is relative to projectRoot, we need to use resolution function

0 commit comments

Comments
 (0)