metro 0.76.4 → 0.76.6

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.
@@ -31,7 +31,8 @@ import type {
31
31
  ResolverInputOptions,
32
32
  SplitBundleOptions,
33
33
  } from './shared/types.flow';
34
- import type {IncomingMessage, ServerResponse} from 'http';
34
+ import type {IncomingMessage} from 'connect';
35
+ import type {ServerResponse} from 'http';
35
36
  import type {CacheStore} from 'metro-cache';
36
37
  import type {ConfigT, RootPerfLogger} from 'metro-config/src/configTypes.flow';
37
38
  import type {
@@ -65,6 +66,8 @@ const {codeFrameColumns} = require('@babel/code-frame');
65
66
  const MultipartResponse = require('./Server/MultipartResponse');
66
67
  const debug = require('debug')('Metro:Server');
67
68
  const fs = require('graceful-fs');
69
+ const invariant = require('invariant');
70
+ const jscSafeUrl = require('jsc-safe-url');
68
71
  const {
69
72
  Logger,
70
73
  Logger: {createActionStartEntry, createActionEndEntry, log},
@@ -476,11 +479,11 @@ class Server {
476
479
  processRequest: (
477
480
  IncomingMessage,
478
481
  ServerResponse,
479
- ((e: ?Error) => mixed),
482
+ ((e: ?Error) => void),
480
483
  ) => void = (
481
484
  req: IncomingMessage,
482
485
  res: ServerResponse,
483
- next: (?Error) => mixed,
486
+ next: (?Error) => void,
484
487
  ) => {
485
488
  this._processRequest(req, res, next).catch(next);
486
489
  };
@@ -489,14 +492,19 @@ class Server {
489
492
  return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
490
493
  }
491
494
 
495
+ _rewriteAndNormalizeUrl(requestUrl: string): string {
496
+ return jscSafeUrl.toNormalUrl(
497
+ this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl)),
498
+ );
499
+ }
500
+
492
501
  async _processRequest(
493
502
  req: IncomingMessage,
494
503
  res: ServerResponse,
495
- next: (?Error) => mixed,
504
+ next: (?Error) => void,
496
505
  ) {
497
506
  const originalUrl = req.url;
498
- req.url = this._config.server.rewriteRequestUrl(req.url);
499
-
507
+ req.url = this._rewriteAndNormalizeUrl(req.url);
500
508
  const urlObj = url.parse(req.url, true);
501
509
  const {host} = req.headers;
502
510
  debug(
@@ -899,7 +907,7 @@ class Server {
899
907
  bundle: bundleCode,
900
908
  };
901
909
  },
902
- finish({req, mres, result}) {
910
+ finish({req, mres, serializerOptions, result}) {
903
911
  if (
904
912
  // We avoid parsing the dates since the client should never send a more
905
913
  // recent date than the one returned by the Delta Bundler (if that's the
@@ -916,6 +924,9 @@ class Server {
916
924
  String(result.numModifiedFiles),
917
925
  );
918
926
  mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
927
+ if (serializerOptions?.sourceUrl != null) {
928
+ mres.setHeader('Content-Location', serializerOptions.sourceUrl);
929
+ }
919
930
  mres.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
920
931
  mres.setHeader('Last-Modified', result.lastModifiedDate.toUTCString());
921
932
  mres.setHeader(
@@ -1112,19 +1123,33 @@ class Server {
1112
1123
  /* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
1113
1124
  const body = await req.rawBody;
1114
1125
  const parsedBody = JSON.parse(body);
1115
- const stack = parsedBody.stack.map(frame => {
1116
- if (frame.file && frame.file.includes('://')) {
1126
+
1127
+ const rewriteAndNormalizeStackFrame = <T>(
1128
+ frame: T,
1129
+ lineNumber: number,
1130
+ ): T => {
1131
+ invariant(
1132
+ frame != null && typeof frame === 'object',
1133
+ 'Bad stack frame at line %d, expected object, received: %s',
1134
+ lineNumber,
1135
+ typeof frame,
1136
+ );
1137
+ const frameFile = frame.file;
1138
+ if (typeof frameFile === 'string' && frameFile.includes('://')) {
1117
1139
  return {
1118
1140
  ...frame,
1119
- file: this._config.server.rewriteRequestUrl(frame.file),
1141
+ file: this._rewriteAndNormalizeUrl(frameFile),
1120
1142
  };
1121
1143
  }
1122
1144
  return frame;
1123
- });
1145
+ };
1146
+
1147
+ const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
1124
1148
  // In case of multiple bundles / HMR, some stack frames can have different URLs from others
1125
1149
  const urls = new Set<string>();
1126
1150
 
1127
1151
  stack.forEach(frame => {
1152
+ // These urls have been rewritten and normalized above.
1128
1153
  const sourceUrl = frame.file;
1129
1154
  // Skip `/debuggerWorker.js` which does not need symbolication.
1130
1155
  if (
@@ -1139,8 +1164,11 @@ class Server {
1139
1164
 
1140
1165
  debug('Getting source maps for symbolication');
1141
1166
  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),
1167
+ Array.from(urls.values()).map(normalizedUrl =>
1168
+ this._explodedSourceMapForBundleOptions(
1169
+ this._parseOptions(normalizedUrl),
1170
+ ),
1171
+ ),
1144
1172
  );
1145
1173
 
1146
1174
  debug('Performing fast symbolication');
@@ -1168,12 +1196,9 @@ class Server {
1168
1196
  }
1169
1197
  }
1170
1198
 
1171
- async _explodedSourceMapForURL(reqUrl: string): Promise<ExplodedSourceMap> {
1172
- const options = parseOptionsFromUrl(
1173
- reqUrl,
1174
- new Set(this._config.resolver.platforms),
1175
- );
1176
-
1199
+ async _explodedSourceMapForBundleOptions(
1200
+ bundleOptions: BundleOptions,
1201
+ ): Promise<ExplodedSourceMap> {
1177
1202
  const {
1178
1203
  entryFile,
1179
1204
  graphOptions,
@@ -1181,7 +1206,7 @@ class Server {
1181
1206
  resolverOptions,
1182
1207
  serializerOptions,
1183
1208
  transformOptions,
1184
- } = splitBundleOptions(options);
1209
+ } = splitBundleOptions(bundleOptions);
1185
1210
 
1186
1211
  /**
1187
1212
  * `entryFile` is relative to projectRoot, we need to use resolution function
package/src/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from './ModuleGraph/worker/collectDependencies';
14
14
  export * from './Server';
15
15
  export * from './lib/reporting';
16
16
 
17
+ import type {HandleFunction} from 'connect';
17
18
  import type {EventEmitter} from 'events';
18
19
  import type {IncomingMessage, Server as HttpServer} from 'http';
19
20
  import type {Server as HttpsServer} from 'https';
@@ -72,6 +73,7 @@ export interface RunServerOptions {
72
73
  /** @deprecated since version 0.61 */
73
74
  secureKey?: string;
74
75
 
76
+ unstable_extraMiddleware?: ReadonlyArray<HandleFunction>;
75
77
  waitForBundler?: boolean;
76
78
  watch?: boolean;
77
79
  websocketEndpoints?: {
@@ -126,7 +128,7 @@ export function runMetro(
126
128
  options?: RunMetroOptions,
127
129
  ): Promise<MetroServer>;
128
130
 
129
- export function createConnectMiddleWare(
131
+ export function createConnectMiddleware(
130
132
  config: ConfigT,
131
133
  options?: RunMetroOptions,
132
134
  ): Promise<MetroMiddleWare>;
package/src/index.flow.js CHANGED
@@ -134,6 +134,7 @@ exports.runServer = async (
134
134
  // deprecated
135
135
  secureKey,
136
136
  // deprecated
137
+ unstable_extraMiddleware,
137
138
  waitForBundler = false,
138
139
  websocketEndpoints = {},
139
140
  watch,
@@ -161,6 +162,9 @@ exports.runServer = async (
161
162
  }
162
163
  );
163
164
  serverApp.use(middleware);
165
+ for (const handler of unstable_extraMiddleware ?? []) {
166
+ serverApp.use(handler);
167
+ }
164
168
  let inspectorProxy = null;
165
169
  if (config.server.runInspectorProxy) {
166
170
  inspectorProxy = new InspectorProxy(config.projectRoot);
@@ -175,6 +179,7 @@ exports.runServer = async (
175
179
  ...secureServerOptions,
176
180
  };
177
181
  }
182
+ // $FlowFixMe[incompatible-call] 'http' and 'https' Flow types do not match
178
183
  httpServer = https.createServer(options, serverApp);
179
184
  } else {
180
185
  httpServer = http.createServer(serverApp);
@@ -11,6 +11,7 @@
11
11
 
12
12
  'use strict';
13
13
 
14
+ import type {HandleFunction} from 'connect';
14
15
  import type {CustomResolverOptions} from 'metro-resolver';
15
16
  import type {ReadOnlyGraph} from './DeltaBundler';
16
17
  import type {ServerOptions} from './Server';
@@ -82,6 +83,7 @@ export type RunServerOptions = $ReadOnly<{
82
83
  secure?: boolean, // deprecated
83
84
  secureCert?: string, // deprecated
84
85
  secureKey?: string, // deprecated
86
+ unstable_extraMiddleware?: $ReadOnlyArray<HandleFunction>,
85
87
  waitForBundler?: boolean,
86
88
  watch?: boolean,
87
89
  websocketEndpoints?: $ReadOnly<{
@@ -254,6 +256,7 @@ exports.runServer = async (
254
256
  secure, //deprecated
255
257
  secureCert, // deprecated
256
258
  secureKey, // deprecated
259
+ unstable_extraMiddleware,
257
260
  waitForBundler = false,
258
261
  websocketEndpoints = {},
259
262
  watch,
@@ -283,6 +286,10 @@ exports.runServer = async (
283
286
 
284
287
  serverApp.use(middleware);
285
288
 
289
+ for (const handler of unstable_extraMiddleware ?? []) {
290
+ serverApp.use(handler);
291
+ }
292
+
286
293
  let inspectorProxy: ?InspectorProxy = null;
287
294
  if (config.server.runInspectorProxy) {
288
295
  inspectorProxy = new InspectorProxy(config.projectRoot);
@@ -299,6 +306,7 @@ exports.runServer = async (
299
306
  ...secureServerOptions,
300
307
  };
301
308
  }
309
+ // $FlowFixMe[incompatible-call] 'http' and 'https' Flow types do not match
302
310
  httpServer = https.createServer(options, serverApp);
303
311
  } else {
304
312
  httpServer = http.createServer(serverApp);
@@ -14,6 +14,7 @@
14
14
  const parsePlatformFilePath = require("../node-haste/lib/parsePlatformFilePath");
15
15
  const parseCustomResolverOptions = require("./parseCustomResolverOptions");
16
16
  const parseCustomTransformOptions = require("./parseCustomTransformOptions");
17
+ const jscSafeUrl = require("jsc-safe-url");
17
18
  const nullthrows = require("nullthrows");
18
19
  const path = require("path");
19
20
  const url = require("url");
@@ -27,8 +28,8 @@ const getTransformProfile = (transformProfile) =>
27
28
  transformProfile === "hermes-stable" || transformProfile === "hermes-canary"
28
29
  ? transformProfile
29
30
  : "default";
30
- module.exports = function parseOptionsFromUrl(requestUrl, platforms) {
31
- const parsedURL = nullthrows(url.parse(requestUrl, true)); // `true` to parse the query param as an object.
31
+ module.exports = function parseOptionsFromUrl(normalizedRequestUrl, platforms) {
32
+ const parsedURL = nullthrows(url.parse(normalizedRequestUrl, true)); // `true` to parse the query param as an object.
32
33
  const query = nullthrows(parsedURL.query);
33
34
  const pathname =
34
35
  query.bundleEntry ||
@@ -62,7 +63,7 @@ module.exports = function parseOptionsFromUrl(requestUrl, platforms) {
62
63
  platform != null && platform.match(/^(android|ios)$/) ? "http" : "",
63
64
  pathname: pathname.replace(/\.(bundle|delta)$/, ".map"),
64
65
  }),
65
- sourceUrl: requestUrl,
66
+ sourceUrl: jscSafeUrl.toJscSafeUrl(normalizedRequestUrl),
66
67
  unstable_transformProfile: getTransformProfile(
67
68
  query.unstable_transformProfile
68
69
  ),
@@ -17,6 +17,7 @@ import type {TransformProfile} from 'metro-babel-transformer';
17
17
  const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
18
18
  const parseCustomResolverOptions = require('./parseCustomResolverOptions');
19
19
  const parseCustomTransformOptions = require('./parseCustomTransformOptions');
20
+ const jscSafeUrl = require('jsc-safe-url');
20
21
  const nullthrows = require('nullthrows');
21
22
  const path = require('path');
22
23
  const url = require('url');
@@ -39,10 +40,10 @@ const getTransformProfile = (transformProfile: string): TransformProfile =>
39
40
  : 'default';
40
41
 
41
42
  module.exports = function parseOptionsFromUrl(
42
- requestUrl: string,
43
+ normalizedRequestUrl: string,
43
44
  platforms: Set<string>,
44
45
  ): BundleOptions {
45
- const parsedURL = nullthrows(url.parse(requestUrl, true)); // `true` to parse the query param as an object.
46
+ const parsedURL = nullthrows(url.parse(normalizedRequestUrl, true)); // `true` to parse the query param as an object.
46
47
  const query = nullthrows(parsedURL.query);
47
48
  const pathname =
48
49
  query.bundleEntry ||
@@ -77,7 +78,7 @@ module.exports = function parseOptionsFromUrl(
77
78
  platform != null && platform.match(/^(android|ios)$/) ? 'http' : '',
78
79
  pathname: pathname.replace(/\.(bundle|delta)$/, '.map'),
79
80
  }),
80
- sourceUrl: requestUrl,
81
+ sourceUrl: jscSafeUrl.toJscSafeUrl(normalizedRequestUrl),
81
82
  unstable_transformProfile: getTransformProfile(
82
83
  query.unstable_transformProfile,
83
84
  ),