Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit JSC-safe URLs in HMR, //# sourceURL, Content-Location #989

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Configuration.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
5 changes: 4 additions & 1 deletion packages/metro/src/DeltaBundler/Serializers/helpers/js.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {MixedOutput, Module} from '../../types.flow';
import type {JsOutput} from 'metro-transform-worker';

const invariant = require('invariant');
const jscSafeUrl = require('jsc-safe-url');
const {addParamsToDefineCall} = require('metro-transform-plugins');
const path = require('path');

Expand Down Expand Up @@ -59,7 +60,9 @@ function getModuleParams(module: Module<>, options: Options): Array<mixed> {
// Construct a server-relative URL for the split bundle, propagating
// most parameters from the main bundle's URL.

const {searchParams} = new URL(options.sourceUrl);
const {searchParams} = new URL(
jscSafeUrl.toNormalUrl(options.sourceUrl),
);
searchParams.set('modulesOnly', 'true');
searchParams.set('runModule', 'false');

Expand Down
3 changes: 2 additions & 1 deletion packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {DeltaResult, Module, ReadOnlyGraph} from '../types.flow';
import type {HmrModule} from 'metro-runtime/src/modules/types.flow';

const {isJsModule, wrapModule} = require('./helpers/js');
const jscSafeUrl = require('jsc-safe-url');
const {addParamsToDefineCall} = require('metro-transform-plugins');
const path = require('path');
const url = require('url');
Expand Down Expand Up @@ -53,7 +54,7 @@ function generateModules(
};

const sourceMappingURL = getURL('map');
const sourceURL = getURL('bundle');
const sourceURL = jscSafeUrl.toJscSafeUrl(getURL('bundle'));
const code =
prepareModule(module, graph, options) +
`\n//# sourceMappingURL=${sourceMappingURL}\n` +
Expand Down
56 changes: 40 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 @@ -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 @@ -899,7 +906,7 @@ class Server {
bundle: bundleCode,
};
},
finish({req, mres, result}) {
finish({req, mres, serializerOptions, result}) {
if (
// We avoid parsing the dates since the client should never send a more
// recent date than the one returned by the Delta Bundler (if that's the
Expand All @@ -916,6 +923,9 @@ class Server {
String(result.numModifiedFiles),
);
mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
if (serializerOptions?.sourceUrl != null) {
mres.setHeader('Content-Location', serializerOptions.sourceUrl);
}
mres.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
mres.setHeader('Last-Modified', result.lastModifiedDate.toUTCString());
mres.setHeader(
Expand Down Expand Up @@ -1112,19 +1122,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 +1163,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 +1195,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
Loading