metro 0.72.2 → 0.72.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metro",
3
- "version": "0.72.2",
3
+ "version": "0.72.4",
4
4
  "description": "🚇 The JavaScript bundler for React Native.",
5
5
  "main": "src/index.js",
6
6
  "bin": "src/cli.js",
@@ -35,23 +35,24 @@
35
35
  "image-size": "^0.6.0",
36
36
  "invariant": "^2.2.4",
37
37
  "jest-worker": "^27.2.0",
38
+ "jsc-safe-url": "^0.2.2",
38
39
  "lodash.throttle": "^4.1.1",
39
- "metro-babel-transformer": "0.72.2",
40
- "metro-cache": "0.72.2",
41
- "metro-cache-key": "0.72.2",
42
- "metro-config": "0.72.2",
43
- "metro-core": "0.72.2",
44
- "metro-file-map": "0.72.2",
45
- "metro-hermes-compiler": "0.72.2",
46
- "metro-inspector-proxy": "0.72.2",
47
- "metro-minify-uglify": "0.72.2",
48
- "metro-react-native-babel-preset": "0.72.2",
49
- "metro-resolver": "0.72.2",
50
- "metro-runtime": "0.72.2",
51
- "metro-source-map": "0.72.2",
52
- "metro-symbolicate": "0.72.2",
53
- "metro-transform-plugins": "0.72.2",
54
- "metro-transform-worker": "0.72.2",
40
+ "metro-babel-transformer": "0.72.4",
41
+ "metro-cache": "0.72.4",
42
+ "metro-cache-key": "0.72.4",
43
+ "metro-config": "0.72.4",
44
+ "metro-core": "0.72.4",
45
+ "metro-file-map": "0.72.4",
46
+ "metro-hermes-compiler": "0.72.4",
47
+ "metro-inspector-proxy": "0.72.4",
48
+ "metro-minify-uglify": "0.72.4",
49
+ "metro-react-native-babel-preset": "0.72.4",
50
+ "metro-resolver": "0.72.4",
51
+ "metro-runtime": "0.72.4",
52
+ "metro-source-map": "0.72.4",
53
+ "metro-symbolicate": "0.72.4",
54
+ "metro-transform-plugins": "0.72.4",
55
+ "metro-transform-worker": "0.72.4",
55
56
  "mime-types": "^2.1.27",
56
57
  "node-fetch": "^2.2.0",
57
58
  "nullthrows": "^1.1.1",
@@ -70,10 +71,10 @@
70
71
  "babel-jest": "^26.6.3",
71
72
  "dedent": "^0.7.0",
72
73
  "jest-snapshot": "^26.5.2",
73
- "metro-babel-register": "0.72.2",
74
- "metro-memory-fs": "0.72.2",
75
- "metro-react-native-babel-preset": "0.72.2",
76
- "metro-react-native-babel-transformer": "0.72.2",
74
+ "metro-babel-register": "0.72.4",
75
+ "metro-memory-fs": "0.72.4",
76
+ "metro-react-native-babel-preset": "0.72.4",
77
+ "metro-react-native-babel-transformer": "0.72.4",
77
78
  "mock-req": "^0.2.0",
78
79
  "mock-res": "^0.6.0",
79
80
  "stack-trace": "^0.0.10"
@@ -11,6 +11,8 @@
11
11
 
12
12
  const { isJsModule, wrapModule } = require("./helpers/js");
13
13
 
14
+ const jscSafeUrl = require("jsc-safe-url");
15
+
14
16
  const { addParamsToDefineCall } = require("metro-transform-plugins");
15
17
 
16
18
  const path = require("path");
@@ -37,7 +39,7 @@ function generateModules(sourceModules, graph, options) {
37
39
  };
38
40
 
39
41
  const sourceMappingURL = getURL("map");
40
- const sourceURL = getURL("bundle");
42
+ const sourceURL = jscSafeUrl.toJscSafeUrl(getURL("bundle"));
41
43
  const code =
42
44
  prepareModule(module, graph, options) +
43
45
  `\n//# sourceMappingURL=${sourceMappingURL}\n` +
@@ -15,6 +15,7 @@ import type {DeltaResult, Graph, Module} from '../types.flow';
15
15
  import type {HmrModule} from 'metro-runtime/src/modules/types.flow';
16
16
 
17
17
  const {isJsModule, wrapModule} = require('./helpers/js');
18
+ const jscSafeUrl = require('jsc-safe-url');
18
19
  const {addParamsToDefineCall} = require('metro-transform-plugins');
19
20
  const path = require('path');
20
21
  const url = require('url');
@@ -50,7 +51,7 @@ function generateModules(
50
51
  };
51
52
 
52
53
  const sourceMappingURL = getURL('map');
53
- const sourceURL = getURL('bundle');
54
+ const sourceURL = jscSafeUrl.toJscSafeUrl(getURL('bundle'));
54
55
  const code =
55
56
  prepareModule(module, graph, options) +
56
57
  `\n//# sourceMappingURL=${sourceMappingURL}\n` +
@@ -13,7 +13,6 @@ export type * from './Worker.flow';
13
13
  */
14
14
 
15
15
  try {
16
- // $FlowFixMe[untyped-import]
17
16
  require("metro-babel-register").unstable_registerForMetroMonorepo();
18
17
  } catch {}
19
18
 
@@ -15,7 +15,6 @@ export type * from './Worker.flow';
15
15
  */
16
16
 
17
17
  try {
18
- // $FlowFixMe[untyped-import]
19
18
  require('metro-babel-register').unstable_registerForMetroMonorepo();
20
19
  } catch {}
21
20
 
@@ -286,8 +286,7 @@ async function processModule(path, graph, delta, options) {
286
286
  invariant(
287
287
  module.dependencies.size === currentDependencies.size,
288
288
  "Failed to add the correct dependencies"
289
- ); // $FlowFixMe[cannot-write]
290
-
289
+ );
291
290
  module.dependencies = currentDependencies;
292
291
  return module;
293
292
  }
@@ -728,10 +727,11 @@ function collectWhite(module, graph, delta) {
728
727
  graph.privateState.gc.color.set(module.path, "black");
729
728
 
730
729
  for (const dependency of module.dependencies.values()) {
731
- const childModule = nullthrows(
732
- graph.dependencies.get(dependency.absolutePath)
733
- );
734
- collectWhite(childModule, graph, delta);
730
+ const childModule = graph.dependencies.get(dependency.absolutePath); // The child may already have been collected.
731
+
732
+ if (childModule) {
733
+ collectWhite(childModule, graph, delta);
734
+ }
735
735
  }
736
736
 
737
737
  freeModule(module, graph, delta);
@@ -345,7 +345,6 @@ async function processModule<T>(
345
345
  'Failed to add the correct dependencies',
346
346
  );
347
347
 
348
- // $FlowFixMe[cannot-write]
349
348
  module.dependencies = currentDependencies;
350
349
 
351
350
  return module;
@@ -812,10 +811,11 @@ function collectWhite<T>(module: Module<T>, graph: Graph<T>, delta: Delta) {
812
811
  ) {
813
812
  graph.privateState.gc.color.set(module.path, 'black');
814
813
  for (const dependency of module.dependencies.values()) {
815
- const childModule = nullthrows(
816
- graph.dependencies.get(dependency.absolutePath),
817
- );
818
- collectWhite(childModule, graph, delta);
814
+ const childModule = graph.dependencies.get(dependency.absolutePath);
815
+ // The child may already have been collected.
816
+ if (childModule) {
817
+ collectWhite(childModule, graph, delta);
818
+ }
819
819
  }
820
820
  freeModule(module, graph, delta);
821
821
  }
@@ -54,7 +54,6 @@ module.exports = class HasteFS {
54
54
 
55
55
  matches(directory, pattern) {
56
56
  const entries = this.directoryEntries.get(directory); // $FlowFixMe[method-unbinding] added when improving typing for this parameters
57
- // $FlowFixMe[incompatible-call]
58
57
 
59
58
  return entries ? entries.filter(pattern.test, pattern) : [];
60
59
  }
@@ -66,7 +66,6 @@ module.exports = class HasteFS {
66
66
  const entries = this.directoryEntries.get(directory);
67
67
 
68
68
  // $FlowFixMe[method-unbinding] added when improving typing for this parameters
69
- // $FlowFixMe[incompatible-call]
70
69
  return entries ? entries.filter(pattern.test, pattern) : [];
71
70
  }
72
71
  };
@@ -25,8 +25,7 @@ function reverseDependencyMapReferences({ types: t }) {
25
25
 
26
26
  if (node.callee.name === `${state.opts.globalPrefix}__d`) {
27
27
  // $FlowFixMe Flow error uncovered by typing Babel more strictly
28
- const lastArg = node.arguments[0].params.slice(-1)[0]; // $FlowFixMe Flow error uncovered by typing Babel more strictly
29
-
28
+ const lastArg = node.arguments[0].params.slice(-1)[0];
30
29
  const depMapName = lastArg && lastArg.name;
31
30
 
32
31
  if (depMapName == null) {
@@ -37,7 +37,6 @@ function reverseDependencyMapReferences({types: t}: {types: Types, ...}): {
37
37
  if (node.callee.name === `${state.opts.globalPrefix}__d`) {
38
38
  // $FlowFixMe Flow error uncovered by typing Babel more strictly
39
39
  const lastArg = node.arguments[0].params.slice(-1)[0];
40
- // $FlowFixMe Flow error uncovered by typing Babel more strictly
41
40
  const depMapName: ?string = lastArg && lastArg.name;
42
41
 
43
42
  if (depMapName == null) {
@@ -314,7 +314,6 @@ export type ResolvedLibrary = {
314
314
  +files: $ReadOnlyArray<$DeepReadOnly<ResolvedCodeFile>>,
315
315
  /* cannot be a Map because it's JSONified later on */
316
316
  +assets: AssetContentsByPath,
317
- +isPartiallyResolved?: boolean,
318
317
  };
319
318
 
320
319
  type DeepReadOnlyFn = (<T>(Array<T>) => $ReadOnlyArray<$DeepReadOnly<T>>) &
@@ -4,6 +4,7 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  *
7
+ *
7
8
  * @format
8
9
  */
9
10
  "use strict";
@@ -14,17 +15,20 @@ const CRLF = "\r\n";
14
15
  const BOUNDARY = "3beqjf3apnqeu3h5jqorms4i";
15
16
 
16
17
  class MultipartResponse {
17
- static wrap(req, res) {
18
+ static wrapIfSupported(req, res) {
18
19
  if (accepts(req).types().includes("multipart/mixed")) {
19
20
  return new MultipartResponse(res);
20
- } // Ugly hack, ideally wrap function should always return a proxy
21
- // object with the same interface
22
-
23
- res.writeChunk = () => {}; // noop
21
+ }
24
22
 
25
23
  return res;
26
24
  }
27
25
 
26
+ static serializeHeaders(headers) {
27
+ return Object.keys(headers)
28
+ .map((key) => `${key}: ${headers[key]}`)
29
+ .join(CRLF);
30
+ }
31
+
28
32
  constructor(res) {
29
33
  this.res = res;
30
34
  this.headers = {};
@@ -47,7 +51,7 @@ class MultipartResponse {
47
51
  this.res.write(MultipartResponse.serializeHeaders(headers) + CRLF + CRLF);
48
52
  }
49
53
 
50
- if (data) {
54
+ if (data != null) {
51
55
  this.res.write(data);
52
56
  }
53
57
 
@@ -78,12 +82,6 @@ class MultipartResponse {
78
82
  this.writeChunk(this.headers, data, true);
79
83
  this.res.end();
80
84
  }
81
-
82
- static serializeHeaders(headers) {
83
- return Object.keys(headers)
84
- .map((key) => `${key}: ${headers[key]}`)
85
- .join(CRLF);
86
- }
87
85
  }
88
86
 
89
87
  module.exports = MultipartResponse;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow strict
8
+ * @format
9
+ */
10
+
11
+ 'use strict';
12
+ import type {IncomingMessage, ServerResponse} from 'http';
13
+ const accepts = require('accepts');
14
+
15
+ const CRLF = '\r\n';
16
+ const BOUNDARY = '3beqjf3apnqeu3h5jqorms4i';
17
+ type Data = string | Buffer | Uint8Array;
18
+ type Headers = {[string]: string | number};
19
+
20
+ class MultipartResponse {
21
+ static wrapIfSupported(
22
+ req: IncomingMessage,
23
+ res: ServerResponse,
24
+ ): MultipartResponse | ServerResponse {
25
+ if (accepts(req).types().includes('multipart/mixed')) {
26
+ return new MultipartResponse(res);
27
+ }
28
+
29
+ return res;
30
+ }
31
+
32
+ static serializeHeaders(headers: Headers): string {
33
+ return Object.keys(headers)
34
+ .map(key => `${key}: ${headers[key]}`)
35
+ .join(CRLF);
36
+ }
37
+
38
+ res: ServerResponse;
39
+ headers: Headers;
40
+
41
+ constructor(res: ServerResponse) {
42
+ this.res = res;
43
+ this.headers = {};
44
+ res.writeHead(200, {
45
+ 'Content-Type': `multipart/mixed; boundary="${BOUNDARY}"`,
46
+ });
47
+ res.write(
48
+ 'If you are seeing this, your client does not support multipart response',
49
+ );
50
+ }
51
+
52
+ writeChunk(
53
+ headers: Headers | null,
54
+ data?: Data,
55
+ isLast?: boolean = false,
56
+ ): void {
57
+ if (this.res.finished) {
58
+ return;
59
+ }
60
+
61
+ this.res.write(`${CRLF}--${BOUNDARY}${CRLF}`);
62
+ if (headers) {
63
+ this.res.write(MultipartResponse.serializeHeaders(headers) + CRLF + CRLF);
64
+ }
65
+
66
+ if (data != null) {
67
+ this.res.write(data);
68
+ }
69
+
70
+ if (isLast) {
71
+ this.res.write(`${CRLF}--${BOUNDARY}--${CRLF}`);
72
+ }
73
+ }
74
+
75
+ writeHead(status: number, headers?: Headers): void {
76
+ // We can't actually change the response HTTP status code
77
+ // because the headers have already been sent
78
+ this.setHeader('X-Http-Status', status);
79
+ if (!headers) {
80
+ return;
81
+ }
82
+ for (const key in headers) {
83
+ this.setHeader(key, headers[key]);
84
+ }
85
+ }
86
+
87
+ setHeader(name: string, value: string | number): void {
88
+ this.headers[name] = value;
89
+ }
90
+
91
+ end(data?: Data): void {
92
+ this.writeChunk(this.headers, data, true);
93
+ this.res.end();
94
+ }
95
+ }
96
+
97
+ module.exports = MultipartResponse;
package/src/Server.js CHANGED
@@ -47,16 +47,20 @@ const transformHelpers = require("./lib/transformHelpers");
47
47
 
48
48
  const parsePlatformFilePath = require("./node-haste/lib/parsePlatformFilePath");
49
49
 
50
- const MultipartResponse = require("./Server/MultipartResponse");
51
-
52
50
  const symbolicate = require("./Server/symbolicate");
53
51
 
54
52
  const { codeFrameColumns } = require("@babel/code-frame");
55
53
 
54
+ const MultipartResponse = require("./Server/MultipartResponse");
55
+
56
56
  const debug = require("debug")("Metro:Server");
57
57
 
58
58
  const fs = require("graceful-fs");
59
59
 
60
+ const invariant = require("invariant");
61
+
62
+ const jscSafeUrl = require("jsc-safe-url");
63
+
60
64
  const {
61
65
  Logger,
62
66
  Logger: { createActionStartEntry, createActionEndEntry, log },
@@ -414,9 +418,15 @@ class Server {
414
418
  );
415
419
  }
416
420
 
421
+ _rewriteAndNormalizeUrl(requestUrl) {
422
+ return jscSafeUrl.toNormalUrl(
423
+ this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl))
424
+ );
425
+ }
426
+
417
427
  async _processRequest(req, res, next) {
418
428
  const originalUrl = req.url;
419
- req.url = this._config.server.rewriteRequestUrl(req.url);
429
+ req.url = this._rewriteAndNormalizeUrl(req.url);
420
430
  const urlObj = url.parse(req.url, true);
421
431
  const { host } = req.headers;
422
432
  debug(
@@ -516,7 +526,7 @@ class Server {
516
526
  return;
517
527
  }
518
528
 
519
- const mres = MultipartResponse.wrap(req, res);
529
+ const mres = MultipartResponse.wrapIfSupported(req, res);
520
530
  const buildID = this.getNewBuildID();
521
531
  let onProgress = null;
522
532
  let lastProgress = -1;
@@ -532,15 +542,17 @@ class Server {
532
542
  // actually different and that have increased from the last one we sent.
533
543
 
534
544
  if (currentProgress > lastProgress || totalFileCount < 10) {
535
- mres.writeChunk(
536
- {
537
- "Content-Type": "application/json",
538
- },
539
- JSON.stringify({
540
- done: transformedFileCount,
541
- total: totalFileCount,
542
- })
543
- ); // The `uncork` called internally in Node via `promise.nextTick()` may not fire
545
+ if (mres instanceof MultipartResponse) {
546
+ mres.writeChunk(
547
+ {
548
+ "Content-Type": "application/json",
549
+ },
550
+ JSON.stringify({
551
+ done: transformedFileCount,
552
+ total: totalFileCount,
553
+ })
554
+ );
555
+ } // The `uncork` called internally in Node via `promise.nextTick()` may not fire
544
556
  // until all of the Promises are resolved because the microtask queue we're
545
557
  // in could be starving the event loop. This can cause a bug where the progress
546
558
  // is not actually sent in the response until after bundling is complete. This
@@ -691,8 +703,6 @@ class Server {
691
703
 
692
704
  const serializer =
693
705
  this._config.serializer.customSerializer ||
694
- /* $FlowFixMe[missing-local-annot] The type annotation(s) required by
695
- * Flow's LTI update could not be added via codemod */
696
706
  ((...args) => bundleToString(baseJSBundle(...args)).code);
697
707
 
698
708
  const bundle = await serializer(
@@ -741,7 +751,7 @@ class Server {
741
751
  };
742
752
  },
743
753
 
744
- finish({ req, mres, result }) {
754
+ finish({ req, mres, serializerOptions, result }) {
745
755
  if (
746
756
  // We avoid parsing the dates since the client should never send a more
747
757
  // recent date than the one returned by the Delta Bundler (if that's the
@@ -758,6 +768,15 @@ class Server {
758
768
  String(result.numModifiedFiles)
759
769
  );
760
770
  mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
771
+
772
+ if (
773
+ (serializerOptions === null || serializerOptions === void 0
774
+ ? void 0
775
+ : serializerOptions.sourceUrl) != null
776
+ ) {
777
+ mres.setHeader("Content-Location", serializerOptions.sourceUrl);
778
+ }
779
+
761
780
  mres.setHeader("Content-Type", "application/javascript; charset=UTF-8");
762
781
  mres.setHeader("Last-Modified", result.lastModifiedDate.toUTCString());
763
782
  mres.setHeader(
@@ -1060,19 +1079,29 @@ class Server {
1060
1079
  /* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
1061
1080
 
1062
1081
  const body = await req.rawBody;
1063
- const stack = JSON.parse(body).stack.map((frame) => {
1064
- if (frame.file && frame.file.includes("://")) {
1065
- return {
1066
- ...frame,
1067
- file: this._config.server.rewriteRequestUrl(frame.file),
1068
- };
1082
+ const parsedBody = JSON.parse(body);
1083
+
1084
+ const rewriteAndNormalizeStackFrame = (frame, lineNumber) => {
1085
+ invariant(
1086
+ frame != null && typeof frame === "object",
1087
+ "Bad stack frame at line %d, expected object, received: %s",
1088
+ lineNumber,
1089
+ typeof frame
1090
+ );
1091
+ const frameFile = frame.file;
1092
+
1093
+ if (typeof frameFile === "string" && frameFile.includes("://")) {
1094
+ return { ...frame, file: this._rewriteAndNormalizeUrl(frameFile) };
1069
1095
  }
1070
1096
 
1071
1097
  return frame;
1072
- }); // In case of multiple bundles / HMR, some stack frames can have different URLs from others
1098
+ };
1099
+
1100
+ const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame); // In case of multiple bundles / HMR, some stack frames can have different URLs from others
1073
1101
 
1074
1102
  const urls = new Set();
1075
1103
  stack.forEach((frame) => {
1104
+ // These urls have been rewritten and normalized above.
1076
1105
  const sourceUrl = frame.file; // Skip `/debuggerWorker.js` which does not need symbolication.
1077
1106
 
1078
1107
  if (
@@ -1086,8 +1115,11 @@ class Server {
1086
1115
  });
1087
1116
  debug("Getting source maps for symbolication");
1088
1117
  const sourceMaps = await Promise.all(
1089
- // $FlowFixMe[method-unbinding] added when improving typing for this parameters
1090
- Array.from(urls.values()).map(this._explodedSourceMapForURL, this)
1118
+ Array.from(urls.values()).map((normalizedUrl) =>
1119
+ this._explodedSourceMapForBundleOptions(
1120
+ this._parseOptions(normalizedUrl)
1121
+ )
1122
+ )
1091
1123
  );
1092
1124
  debug("Performing fast symbolication");
1093
1125
  const symbolicatedStack = await await symbolicate(
@@ -1116,12 +1148,7 @@ class Server {
1116
1148
  }
1117
1149
  }
1118
1150
 
1119
- async _explodedSourceMapForURL(reqUrl) {
1120
- const options = parseOptionsFromUrl(
1121
- reqUrl,
1122
- new Set(this._config.resolver.platforms),
1123
- getBytecodeVersion()
1124
- );
1151
+ async _explodedSourceMapForBundleOptions(bundleOptions) {
1125
1152
  const {
1126
1153
  entryFile,
1127
1154
  graphOptions,
@@ -1129,7 +1156,7 @@ class Server {
1129
1156
  resolverOptions,
1130
1157
  serializerOptions,
1131
1158
  transformOptions,
1132
- } = splitBundleOptions(options);
1159
+ } = splitBundleOptions(bundleOptions);
1133
1160
  /**
1134
1161
  * `entryFile` is relative to projectRoot, we need to use resolution function
1135
1162
  * to find the appropriate file with supported extensions.
@@ -60,11 +60,13 @@ const parseOptionsFromUrl = require('./lib/parseOptionsFromUrl');
60
60
  const splitBundleOptions = require('./lib/splitBundleOptions');
61
61
  const transformHelpers = require('./lib/transformHelpers');
62
62
  const parsePlatformFilePath = require('./node-haste/lib/parsePlatformFilePath');
63
- const MultipartResponse = require('./Server/MultipartResponse');
64
63
  const symbolicate = require('./Server/symbolicate');
65
64
  const {codeFrameColumns} = require('@babel/code-frame');
65
+ const MultipartResponse = require('./Server/MultipartResponse');
66
66
  const debug = require('debug')('Metro:Server');
67
67
  const fs = require('graceful-fs');
68
+ const invariant = require('invariant');
69
+ const jscSafeUrl = require('jsc-safe-url');
68
70
  const {
69
71
  Logger,
70
72
  Logger: {createActionStartEntry, createActionEndEntry, log},
@@ -91,8 +93,7 @@ type ProcessStartContext = {
91
93
  +bundleOptions: BundleOptions,
92
94
  +graphId: GraphId,
93
95
  +graphOptions: GraphOptions,
94
- // $FlowFixMe[value-as-type]
95
- +mres: MultipartResponse,
96
+ +mres: MultipartResponse | ServerResponse,
96
97
  +req: IncomingMessage,
97
98
  +revisionId?: ?RevisionId,
98
99
  ...SplitBundleOptions,
@@ -475,14 +476,19 @@ class Server {
475
476
  );
476
477
  }
477
478
 
479
+ _rewriteAndNormalizeUrl(requestUrl: string): string {
480
+ return jscSafeUrl.toNormalUrl(
481
+ this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
482
+ );
483
+ }
484
+
478
485
  async _processRequest(
479
486
  req: IncomingMessage,
480
487
  res: ServerResponse,
481
488
  next: (?Error) => mixed,
482
489
  ) {
483
490
  const originalUrl = req.url;
484
- req.url = this._config.server.rewriteRequestUrl(req.url);
485
-
491
+ req.url = this._rewriteAndNormalizeUrl(req.url);
486
492
  const urlObj = url.parse(req.url, true);
487
493
  const {host} = req.headers;
488
494
  debug(
@@ -601,7 +607,7 @@ class Server {
601
607
  return;
602
608
  }
603
609
 
604
- const mres = MultipartResponse.wrap(req, res);
610
+ const mres = MultipartResponse.wrapIfSupported(req, res);
605
611
  const buildID = this.getNewBuildID();
606
612
 
607
613
  let onProgress = null;
@@ -618,13 +624,15 @@ class Server {
618
624
  // that, we check the percentage, and only send percentages that are
619
625
  // actually different and that have increased from the last one we sent.
620
626
  if (currentProgress > lastProgress || totalFileCount < 10) {
621
- mres.writeChunk(
622
- {'Content-Type': 'application/json'},
623
- JSON.stringify({
624
- done: transformedFileCount,
625
- total: totalFileCount,
626
- }),
627
- );
627
+ if (mres instanceof MultipartResponse) {
628
+ mres.writeChunk(
629
+ {'Content-Type': 'application/json'},
630
+ JSON.stringify({
631
+ done: transformedFileCount,
632
+ total: totalFileCount,
633
+ }),
634
+ );
635
+ }
628
636
 
629
637
  // The `uncork` called internally in Node via `promise.nextTick()` may not fire
630
638
  // until all of the Promises are resolved because the microtask queue we're
@@ -789,8 +797,6 @@ class Server {
789
797
 
790
798
  const serializer =
791
799
  this._config.serializer.customSerializer ||
792
- /* $FlowFixMe[missing-local-annot] The type annotation(s) required by
793
- * Flow's LTI update could not be added via codemod */
794
800
  ((...args) => bundleToString(baseJSBundle(...args)).code);
795
801
 
796
802
  const bundle = await serializer(
@@ -836,7 +842,7 @@ class Server {
836
842
  bundle: bundleCode,
837
843
  };
838
844
  },
839
- finish({req, mres, result}) {
845
+ finish({req, mres, serializerOptions, result}) {
840
846
  if (
841
847
  // We avoid parsing the dates since the client should never send a more
842
848
  // recent date than the one returned by the Delta Bundler (if that's the
@@ -853,6 +859,9 @@ class Server {
853
859
  String(result.numModifiedFiles),
854
860
  );
855
861
  mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
862
+ if (serializerOptions?.sourceUrl != null) {
863
+ mres.setHeader('Content-Location', serializerOptions.sourceUrl);
864
+ }
856
865
  mres.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
857
866
  mres.setHeader('Last-Modified', result.lastModifiedDate.toUTCString());
858
867
  mres.setHeader(
@@ -1146,19 +1155,34 @@ class Server {
1146
1155
  debug('Start symbolication');
1147
1156
  /* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
1148
1157
  const body = await req.rawBody;
1149
- const stack = JSON.parse(body).stack.map(frame => {
1150
- if (frame.file && frame.file.includes('://')) {
1158
+ const parsedBody = JSON.parse(body);
1159
+
1160
+ const rewriteAndNormalizeStackFrame = <T>(
1161
+ frame: T,
1162
+ lineNumber: number,
1163
+ ): T => {
1164
+ invariant(
1165
+ frame != null && typeof frame === 'object',
1166
+ 'Bad stack frame at line %d, expected object, received: %s',
1167
+ lineNumber,
1168
+ typeof frame,
1169
+ );
1170
+ const frameFile = frame.file;
1171
+ if (typeof frameFile === 'string' && frameFile.includes('://')) {
1151
1172
  return {
1152
1173
  ...frame,
1153
- file: this._config.server.rewriteRequestUrl(frame.file),
1174
+ file: this._rewriteAndNormalizeUrl(frameFile),
1154
1175
  };
1155
1176
  }
1156
1177
  return frame;
1157
- });
1178
+ };
1179
+
1180
+ const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
1158
1181
  // In case of multiple bundles / HMR, some stack frames can have different URLs from others
1159
1182
  const urls = new Set();
1160
1183
 
1161
1184
  stack.forEach(frame => {
1185
+ // These urls have been rewritten and normalized above.
1162
1186
  const sourceUrl = frame.file;
1163
1187
  // Skip `/debuggerWorker.js` which does not need symbolication.
1164
1188
  if (
@@ -1173,8 +1197,11 @@ class Server {
1173
1197
 
1174
1198
  debug('Getting source maps for symbolication');
1175
1199
  const sourceMaps = await Promise.all(
1176
- // $FlowFixMe[method-unbinding] added when improving typing for this parameters
1177
- Array.from(urls.values()).map(this._explodedSourceMapForURL, this),
1200
+ Array.from(urls.values()).map(normalizedUrl =>
1201
+ this._explodedSourceMapForBundleOptions(
1202
+ this._parseOptions(normalizedUrl),
1203
+ ),
1204
+ ),
1178
1205
  );
1179
1206
 
1180
1207
  debug('Performing fast symbolication');
@@ -1201,13 +1228,9 @@ class Server {
1201
1228
  }
1202
1229
  }
1203
1230
 
1204
- async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1205
- const options = parseOptionsFromUrl(
1206
- reqUrl,
1207
- new Set(this._config.resolver.platforms),
1208
- getBytecodeVersion(),
1209
- );
1210
-
1231
+ async _explodedSourceMapForBundleOptions(
1232
+ bundleOptions: BundleOptions,
1233
+ ): Promise<ExplodedSourceMap> {
1211
1234
  const {
1212
1235
  entryFile,
1213
1236
  graphOptions,
@@ -1215,7 +1238,7 @@ class Server {
1215
1238
  resolverOptions,
1216
1239
  serializerOptions,
1217
1240
  transformOptions,
1218
- } = splitBundleOptions(options);
1241
+ } = splitBundleOptions(bundleOptions);
1219
1242
 
1220
1243
  /**
1221
1244
  * `entryFile` is relative to projectRoot, we need to use resolution function
package/src/index.js CHANGED
@@ -13,7 +13,6 @@ export type * from './index.flow';
13
13
  */
14
14
 
15
15
  try {
16
- // $FlowFixMe[untyped-import]
17
16
  require("metro-babel-register").unstable_registerForMetroMonorepo();
18
17
  } catch {}
19
18
 
package/src/index.js.flow CHANGED
@@ -15,7 +15,6 @@ export type * from './index.flow';
15
15
  */
16
16
 
17
17
  try {
18
- // $FlowFixMe[untyped-import]
19
18
  require('metro-babel-register').unstable_registerForMetroMonorepo();
20
19
  } catch {}
21
20
 
@@ -68,7 +68,6 @@ class CountingSet {
68
68
  }
69
69
  } // Iterate over unique entries
70
70
  // $FlowIssue[unsupported-syntax]
71
- // $FlowFixMe[missing-local-annot]
72
71
 
73
72
  [Symbol.iterator]() {
74
73
  return this.values();
@@ -81,7 +81,6 @@ export default class CountingSet<T> implements ReadOnlyCountingSet<T> {
81
81
 
82
82
  // Iterate over unique entries
83
83
  // $FlowIssue[unsupported-syntax]
84
- // $FlowFixMe[missing-local-annot]
85
84
  [Symbol.iterator](): Iterator<T> {
86
85
  return this.values();
87
86
  }
@@ -144,28 +144,21 @@ class TerminalReporter {
144
144
 
145
145
  _logInitializing(port, hasReducedPerformance) {
146
146
  const logo = [
147
- " ",
148
- " ####### ",
149
- " ################ ",
150
- " ######### ######### ",
151
- " ######### ########## ",
152
- " ######### ###### ######### ",
153
- " ########################################## ",
154
- " ##### ##################### ##### ",
155
- " ##### ############## ##### ",
156
- " ##### ### ###### ### ##### ",
157
- " ##### ####### ####### ##### ",
158
- " ##### ########### ########### ##### ",
159
- " ##### ########################## ##### ",
160
- " ##### ########################## ##### ",
161
- " ##### ###################### ###### ",
162
- " ###### ############# ####### ",
163
- " ######### #### ######### ",
164
- " ######### ######### ",
165
- " ######### ######### ",
166
- " ######### ",
167
- " ",
168
- " ",
147
+ "",
148
+ " ▒▒▓▓▓▓▒▒",
149
+ " ▒▓▓▓▒▒░░▒▒▓▓▓▒",
150
+ " ▒▓▓▓▓░░░▒▒▒▒░░░▓▓▓▓▒",
151
+ " ▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓",
152
+ " ▓▓░░░░░▒▓▓▓▓▓▓▒░░░░░▓▓",
153
+ " ▓▓░░▓▓▒░░░▒▒░░░▒▓▒░░▓▓",
154
+ " ▓▓░░▓▓▓▓▓▒▒▒▒▓▓▓▓▒░░▓▓",
155
+ " ▓▓░░▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░▓▓",
156
+ " ▓▓▒░░▒▒▓▓▓▓▓▓▓▓▒░░░▒▓▓",
157
+ " ▒▓▓▓▒░░░▒▓▓▒░░░▒▓▓▓▒",
158
+ " ▒▓▓▓▒░░░░▒▓▓▓▒",
159
+ " ▒▒▓▓▓▓▒▒",
160
+ "",
161
+ "",
169
162
  ];
170
163
  const color = hasReducedPerformance ? chalk.red : chalk.blue;
171
164
  this.terminal.log(color(logo.join("\n")));
@@ -258,9 +251,14 @@ class TerminalReporter {
258
251
 
259
252
  case "dep_graph_loading":
260
253
  const color = event.hasReducedPerformance ? chalk.red : chalk.blue;
254
+
255
+ const version = "v" + require("../../package.json").version;
256
+
261
257
  this.terminal.log(
262
- color.bold(" Welcome to Metro!\n") +
263
- chalk.dim(" Fast - Scalable - Integrated\n\n")
258
+ color.bold(
259
+ " ".repeat(19 - version.length / 2),
260
+ "Welcome to Metro " + chalk.white(version) + "\n"
261
+ ) + chalk.dim(" Fast - Scalable - Integrated\n\n")
264
262
  );
265
263
 
266
264
  if (event.hasReducedPerformance) {
@@ -182,28 +182,21 @@ class TerminalReporter {
182
182
 
183
183
  _logInitializing(port: number, hasReducedPerformance: boolean): void {
184
184
  const logo = [
185
- ' ',
186
- ' ####### ',
187
- ' ################ ',
188
- ' ######### ######### ',
189
- ' ######### ########## ',
190
- ' ######### ###### ######### ',
191
- ' ########################################## ',
192
- ' ##### ##################### ##### ',
193
- ' ##### ############## ##### ',
194
- ' ##### ### ###### ### ##### ',
195
- ' ##### ####### ####### ##### ',
196
- ' ##### ########### ########### ##### ',
197
- ' ##### ########################## ##### ',
198
- ' ##### ########################## ##### ',
199
- ' ##### ###################### ###### ',
200
- ' ###### ############# ####### ',
201
- ' ######### #### ######### ',
202
- ' ######### ######### ',
203
- ' ######### ######### ',
204
- ' ######### ',
205
- ' ',
206
- ' ',
185
+ '',
186
+ ' ▒▒▓▓▓▓▒▒',
187
+ ' ▒▓▓▓▒▒░░▒▒▓▓▓▒',
188
+ ' ▒▓▓▓▓░░░▒▒▒▒░░░▓▓▓▓▒',
189
+ ' ▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓',
190
+ ' ▓▓░░░░░▒▓▓▓▓▓▓▒░░░░░▓▓',
191
+ ' ▓▓░░▓▓▒░░░▒▒░░░▒▓▒░░▓▓',
192
+ ' ▓▓░░▓▓▓▓▓▒▒▒▒▓▓▓▓▒░░▓▓',
193
+ ' ▓▓░░▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░▓▓',
194
+ ' ▓▓▒░░▒▒▓▓▓▓▓▓▓▓▒░░░▒▓▓',
195
+ ' ▒▓▓▓▒░░░▒▓▓▒░░░▒▓▓▓▒',
196
+ ' ▒▓▓▓▒░░░░▒▓▓▓▒',
197
+ ' ▒▒▓▓▓▓▒▒',
198
+ '',
199
+ '',
207
200
  ];
208
201
 
209
202
  const color = hasReducedPerformance ? chalk.red : chalk.blue;
@@ -275,9 +268,12 @@ class TerminalReporter {
275
268
  break;
276
269
  case 'dep_graph_loading':
277
270
  const color = event.hasReducedPerformance ? chalk.red : chalk.blue;
271
+ const version = 'v' + require('../../package.json').version;
278
272
  this.terminal.log(
279
- color.bold(' Welcome to Metro!\n') +
280
- chalk.dim(' Fast - Scalable - Integrated\n\n'),
273
+ color.bold(
274
+ ' '.repeat(19 - version.length / 2),
275
+ 'Welcome to Metro ' + chalk.white(version) + '\n',
276
+ ) + chalk.dim(' Fast - Scalable - Integrated\n\n'),
281
277
  );
282
278
 
283
279
  if (event.hasReducedPerformance) {
@@ -15,6 +15,8 @@ const parseCustomResolverOptions = require("./parseCustomResolverOptions");
15
15
 
16
16
  const parseCustomTransformOptions = require("./parseCustomTransformOptions");
17
17
 
18
+ const jscSafeUrl = require("jsc-safe-url");
19
+
18
20
  const nullthrows = require("nullthrows");
19
21
 
20
22
  const path = require("path");
@@ -40,11 +42,11 @@ const getTransformProfile = (transformProfile) =>
40
42
  : "default";
41
43
 
42
44
  module.exports = function parseOptionsFromUrl(
43
- requestUrl,
45
+ normalizedRequestUrl,
44
46
  platforms,
45
47
  bytecodeVersion
46
48
  ) {
47
- const parsedURL = nullthrows(url.parse(requestUrl, true)); // `true` to parse the query param as an object.
49
+ const parsedURL = nullthrows(url.parse(normalizedRequestUrl, true)); // `true` to parse the query param as an object.
48
50
 
49
51
  const query = nullthrows(parsedURL.query);
50
52
  const pathname =
@@ -85,7 +87,7 @@ module.exports = function parseOptionsFromUrl(
85
87
  platform != null && platform.match(/^(android|ios)$/) ? "http" : "",
86
88
  pathname: pathname.replace(/\.(bundle|delta)$/, ".map"),
87
89
  }),
88
- sourceUrl: requestUrl,
90
+ sourceUrl: jscSafeUrl.toJscSafeUrl(normalizedRequestUrl),
89
91
  unstable_transformProfile: getTransformProfile(
90
92
  query.unstable_transformProfile
91
93
  ),
@@ -16,6 +16,7 @@ import type {TransformProfile} from 'metro-babel-transformer';
16
16
  const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
17
17
  const parseCustomResolverOptions = require('./parseCustomResolverOptions');
18
18
  const parseCustomTransformOptions = require('./parseCustomTransformOptions');
19
+ const jscSafeUrl = require('jsc-safe-url');
19
20
  const nullthrows = require('nullthrows');
20
21
  const path = require('path');
21
22
  const url = require('url');
@@ -47,11 +48,11 @@ const getTransformProfile = (transformProfile: string): TransformProfile =>
47
48
  : 'default';
48
49
 
49
50
  module.exports = function parseOptionsFromUrl(
50
- requestUrl: string,
51
+ normalizedRequestUrl: string,
51
52
  platforms: Set<string>,
52
53
  bytecodeVersion: number,
53
54
  ): BundleOptions {
54
- const parsedURL = nullthrows(url.parse(requestUrl, true)); // `true` to parse the query param as an object.
55
+ const parsedURL = nullthrows(url.parse(normalizedRequestUrl, true)); // `true` to parse the query param as an object.
55
56
  const query = nullthrows(parsedURL.query);
56
57
  const pathname =
57
58
  query.bundleEntry ||
@@ -92,7 +93,7 @@ module.exports = function parseOptionsFromUrl(
92
93
  platform != null && platform.match(/^(android|ios)$/) ? 'http' : '',
93
94
  pathname: pathname.replace(/\.(bundle|delta)$/, '.map'),
94
95
  }),
95
- sourceUrl: requestUrl,
96
+ sourceUrl: jscSafeUrl.toJscSafeUrl(normalizedRequestUrl),
96
97
  unstable_transformProfile: getTransformProfile(
97
98
  query.unstable_transformProfile,
98
99
  ),
@@ -63,7 +63,6 @@ class Package {
63
63
  }
64
64
  }
65
65
  }
66
- /* $FlowFixMe: `getReplacements` doesn't validate the return value. */
67
66
 
68
67
  return path.join(this._root, main);
69
68
  }
@@ -77,7 +77,6 @@ class Package {
77
77
  }
78
78
  }
79
79
 
80
- /* $FlowFixMe: `getReplacements` doesn't validate the return value. */
81
80
  return path.join(this._root, main);
82
81
  }
83
82
 
@@ -13,7 +13,6 @@ export type * from './bundle.flow';
13
13
  */
14
14
 
15
15
  try {
16
- // $FlowFixMe[untyped-import]
17
16
  require("metro-babel-register").unstable_registerForMetroMonorepo();
18
17
  } catch {}
19
18
 
@@ -15,7 +15,6 @@ export type * from './bundle.flow';
15
15
  */
16
16
 
17
17
  try {
18
- // $FlowFixMe[untyped-import]
19
18
  require('metro-babel-register').unstable_registerForMetroMonorepo();
20
19
  } catch {}
21
20