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 committed Jun 3, 2023
1 parent f9aaca6 commit 8303c38
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 240 deletions.
2 changes: 1 addition & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,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
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"image-size": "^0.6.0",
"invariant": "^2.2.4",
"jest-worker": "^27.2.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.72.3",
"metro-cache": "0.72.3",
Expand Down
53 changes: 37 additions & 16 deletions packages/metro/src/Server.js
Original file line number Diff line number Diff line change
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 @@ -474,14 +476,19 @@ class Server {
);
}

_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 @@ -1145,19 +1152,34 @@ class Server {
debug('Start symbolication');
/* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
const body = await req.rawBody;
const stack = JSON.parse(body).stack.map(frame => {
if (frame.file && frame.file.includes('://')) {
const parsedBody = JSON.parse(body);

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();

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 @@ -1172,8 +1194,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 All @@ -1200,21 +1225,17 @@ class Server {
}
}

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

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
Loading

0 comments on commit 8303c38

Please sign in to comment.