Skip to content

Commit bd357c8

Browse files
robhoganfacebook-github-bot
authored andcommitted
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 905d773 commit bd357c8

File tree

6 files changed

+312
-262
lines changed

6 files changed

+312
-262
lines changed

docs/Configuration.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -615,7 +615,7 @@ The possibility to add custom middleware to the server response chain.
615615
616616
Type: `string => string`
617617
618-
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.
618+
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.
619619
620620
#### `runInspectorProxy`
621621

packages/metro/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"image-size": "^1.0.2",
3434
"invariant": "^2.2.4",
3535
"jest-worker": "^27.2.0",
36+
"jsc-safe-url": "^0.2.2",
3637
"lodash.throttle": "^4.1.1",
3738
"metro-babel-transformer": "0.76.4",
3839
"metro-cache": "0.76.4",

packages/metro/src/Server.js

+36-15
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},
@@ -489,14 +491,19 @@ class Server {
489491
return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
490492
}
491493

494+
_rewriteAndNormalizeUrl(requestUrl: string): string {
495+
return jscSafeUrl.toNormalUrl(
496+
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
497+
);
498+
}
499+
492500
async _processRequest(
493501
req: IncomingMessage,
494502
res: ServerResponse,
495503
next: (?Error) => mixed,
496504
) {
497505
const originalUrl = req.url;
498-
req.url = this._config.server.rewriteRequestUrl(req.url);
499-
506+
req.url = this._rewriteAndNormalizeUrl(req.url);
500507
const urlObj = url.parse(req.url, true);
501508
const {host} = req.headers;
502509
debug(
@@ -1112,19 +1119,33 @@ class Server {
11121119
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
11131120
const body = await req.rawBody;
11141121
const parsedBody = JSON.parse(body);
1115-
const stack = parsedBody.stack.map(frame => {
1116-
if (frame.file && frame.file.includes('://')) {
1122+
1123+
const rewriteAndNormalizeStackFrame = <T>(
1124+
frame: T,
1125+
lineNumber: number,
1126+
): T => {
1127+
invariant(
1128+
frame != null && typeof frame === 'object',
1129+
'Bad stack frame at line %d, expected object, received: %s',
1130+
lineNumber,
1131+
typeof frame,
1132+
);
1133+
const frameFile = frame.file;
1134+
if (typeof frameFile === 'string' && frameFile.includes('://')) {
11171135
return {
11181136
...frame,
1119-
file: this._config.server.rewriteRequestUrl(frame.file),
1137+
file: this._rewriteAndNormalizeUrl(frameFile),
11201138
};
11211139
}
11221140
return frame;
1123-
});
1141+
};
1142+
1143+
const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
11241144
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
11251145
const urls = new Set<string>();
11261146

11271147
stack.forEach(frame => {
1148+
// These urls have been rewritten and normalized above.
11281149
const sourceUrl = frame.file;
11291150
// Skip `/debuggerWorker.js` which does not need symbolication.
11301151
if (
@@ -1139,8 +1160,11 @@ class Server {
11391160

11401161
debug('Getting source maps for symbolication');
11411162
const sourceMaps = await Promise.all(
1142-
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
1143-
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
1163+
Array.from(urls.values()).map(normalizedUrl =>
1164+
this._explodedSourceMapForBundleOptions(
1165+
this._parseOptions(normalizedUrl),
1166+
),
1167+
),
11441168
);
11451169

11461170
debug('Performing fast symbolication');
@@ -1168,20 +1192,17 @@ class Server {
11681192
}
11691193
}
11701194

1171-
async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1172-
const options = parseOptionsFromUrl(
1173-
reqUrl,
1174-
new Set(this._config.resolver.platforms),
1175-
);
1176-
1195+
async _explodedSourceMapForBundleOptions(
1196+
bundleOptions: BundleOptions,
1197+
): Promise<ExplodedSourceMap> {
11771198
const {
11781199
entryFile,
11791200
graphOptions,
11801201
onProgress,
11811202
resolverOptions,
11821203
serializerOptions,
11831204
transformOptions,
1184-
} = splitBundleOptions(options);
1205+
} = splitBundleOptions(bundleOptions);
11851206

11861207
/**
11871208
* `entryFile` is relative to projectRoot, we need to use resolution function

0 commit comments

Comments
 (0)