Skip to content

Commit

Permalink
Accept bundle and symbolication requests in JSC-safe format (//& in…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
robhogan authored and facebook-github-bot committed May 24, 2023
1 parent 905d773 commit bd357c8
Show file tree
Hide file tree
Showing 6 changed files with 312 additions and 262 deletions.
2 changes: 1 addition & 1 deletion docs/Configuration.md
Expand Up @@ -615,7 +615,7 @@ The possibility to add custom middleware to the server response chain.
Type: `string => string`
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.
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.
#### `runInspectorProxy`
Expand Down
1 change: 1 addition & 0 deletions packages/metro/package.json
Expand Up @@ -33,6 +33,7 @@
"image-size": "^1.0.2",
"invariant": "^2.2.4",
"jest-worker": "^27.2.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.76.4",
"metro-cache": "0.76.4",
Expand Down
51 changes: 36 additions & 15 deletions packages/metro/src/Server.js
Expand Up @@ -65,6 +65,8 @@ const {codeFrameColumns} = require('@babel/code-frame');
const MultipartResponse = require('./Server/MultipartResponse');
const debug = require('debug')('Metro:Server');
const fs = require('graceful-fs');
const invariant = require('invariant');
const jscSafeUrl = require('jsc-safe-url');
const {
Logger,
Logger: {createActionStartEntry, createActionEndEntry, log},
Expand Down Expand Up @@ -489,14 +491,19 @@ class Server {
return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
}

_rewriteAndNormalizeUrl(requestUrl: string): string {
return jscSafeUrl.toNormalUrl(
this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
);
}

async _processRequest(
req: IncomingMessage,
res: ServerResponse,
next: (?Error) => mixed,
) {
const originalUrl = req.url;
req.url = this._config.server.rewriteRequestUrl(req.url);

req.url = this._rewriteAndNormalizeUrl(req.url);
const urlObj = url.parse(req.url, true);
const {host} = req.headers;
debug(
Expand Down Expand Up @@ -1112,19 +1119,33 @@ class Server {
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
const body = await req.rawBody;
const parsedBody = JSON.parse(body);
const stack = parsedBody.stack.map(frame => {
if (frame.file && frame.file.includes('://')) {

const rewriteAndNormalizeStackFrame = <T>(
frame: T,
lineNumber: number,
): T => {
invariant(
frame != null && typeof frame === 'object',
'Bad stack frame at line %d, expected object, received: %s',
lineNumber,
typeof frame,
);
const frameFile = frame.file;
if (typeof frameFile === 'string' && frameFile.includes('://')) {
return {
...frame,
file: this._config.server.rewriteRequestUrl(frame.file),
file: this._rewriteAndNormalizeUrl(frameFile),
};
}
return frame;
});
};

const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
// In case of multiple bundles / HMR, some stack frames can have different URLs from others
const urls = new Set<string>();

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

debug('Getting source maps for symbolication');
const sourceMaps = await Promise.all(
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
Array.from(urls.values()).map(normalizedUrl =>
this._explodedSourceMapForBundleOptions(
this._parseOptions(normalizedUrl),
),
),
);

debug('Performing fast symbolication');
Expand Down Expand Up @@ -1168,20 +1192,17 @@ class Server {
}
}

async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
const options = parseOptionsFromUrl(
reqUrl,
new Set(this._config.resolver.platforms),
);

async _explodedSourceMapForBundleOptions(
bundleOptions: BundleOptions,
): Promise<ExplodedSourceMap> {
const {
entryFile,
graphOptions,
onProgress,
resolverOptions,
serializerOptions,
transformOptions,
} = splitBundleOptions(options);
} = splitBundleOptions(bundleOptions);

/**
* `entryFile` is relative to projectRoot, we need to use resolution function
Expand Down

0 comments on commit bd357c8

Please sign in to comment.