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.
@@ -137,16 +137,23 @@ class Graph {
137
137
 
138
138
  // Record the paths that are part of the dependency graph before we start
139
139
  // traversing - we'll use this to ensure we don't report modules modified
140
- // that only exist as part of the graph mid-traversal.
141
- const existingPaths = paths.filter((path) => this.dependencies.has(path));
142
- for (const path of existingPaths) {
140
+ // that only exist as part of the graph mid-traversal, and to eliminate
141
+ // modules that end up in the same state that they started from the delta.
142
+ const originalModules = new Map();
143
+ for (const path of paths) {
144
+ const originalModule = this.dependencies.get(path);
145
+ if (originalModule) {
146
+ originalModules.set(path, originalModule);
147
+ }
148
+ }
149
+ for (const [path] of originalModules) {
143
150
  // Traverse over modules that are part of the dependency graph.
144
151
  //
145
152
  // Note: A given path may not be part of the graph *at this time*, in
146
153
  // particular it may have been removed since we started traversing, but
147
154
  // in that case the path will be visited if and when we add it back to
148
155
  // the graph in a subsequent iteration.
149
- if (this.dependencies.get(path)) {
156
+ if (this.dependencies.has(path)) {
150
157
  await this._traverseDependenciesForSingleFile(
151
158
  path,
152
159
  delta,
@@ -165,17 +172,35 @@ class Graph {
165
172
  // but may not actually differ, may be new, or may have been deleted after
166
173
  // processing. The actually-modified modules are the intersection of
167
174
  // delta.modified with the pre-existing paths, minus modules deleted.
168
- for (const path of existingPaths) {
175
+ for (const [path, originalModule] of originalModules) {
169
176
  invariant(
170
177
  !delta.added.has(path),
171
178
  "delta.added has %s, but this path was already in the graph.",
172
179
  path
173
180
  );
174
181
  if (delta.modified.has(path)) {
175
- // Only report a module as modified if we're not already reporting it
176
- // as added or deleted.
182
+ // It's expected that a module may be both modified and subsequently
183
+ // deleted - we'll only return it as deleted.
177
184
  if (!delta.deleted.has(path)) {
178
- modified.set(path, nullthrows(this.dependencies.get(path)));
185
+ // If a module existed before and has not been deleted, it must be
186
+ // in the dependencies map.
187
+ const newModule = nullthrows(this.dependencies.get(path));
188
+ if (
189
+ // Module.dependencies is mutable, so it's not obviously the case
190
+ // that referential equality implies no modification. However, we
191
+ // only mutate dependencies in two cases:
192
+ // 1. Within _processModule. In that case, we always mutate a new
193
+ // module and set a new reference in this.dependencies.
194
+ // 2. During _releaseModule, when recursively removing
195
+ // dependencies. In that case, we immediately discard the module
196
+ // object.
197
+ // TODO: Refactor for more explicit immutability
198
+ newModule !== originalModule ||
199
+ transfromOutputMayDiffer(newModule, originalModule) ||
200
+ !allDependenciesEqual(newModule, originalModule)
201
+ ) {
202
+ modified.set(path, newModule);
203
+ }
179
204
  }
180
205
  }
181
206
  }
@@ -226,10 +251,6 @@ class Graph {
226
251
  async _processModule(path, delta, options) {
227
252
  const resolvedContext = this.#resolvedContexts.get(path);
228
253
 
229
- // Mark any module processed as potentially modified. Once we've finished
230
- // traversing we'll filter this set down.
231
- delta.modified.add(path);
232
-
233
254
  // Transform the file via the given option.
234
255
  // TODO: Unbind the transform method from options
235
256
  const result = await options.transform(path, resolvedContext);
@@ -241,47 +262,63 @@ class Graph {
241
262
  result.dependencies,
242
263
  options
243
264
  );
244
- const previousModule = this.dependencies.get(path) || {
245
- inverseDependencies:
246
- delta.earlyInverseDependencies.get(path) || new _CountingSet.default(),
247
- path,
248
- };
249
- const previousDependencies = previousModule.dependencies || new Map();
250
-
251
- // Update the module information.
252
- const module = {
253
- ...previousModule,
265
+ const previousModule = this.dependencies.get(path);
266
+ const previousDependencies = previousModule?.dependencies ?? new Map();
267
+ const nextModule = {
268
+ ...(previousModule ?? {
269
+ inverseDependencies:
270
+ delta.earlyInverseDependencies.get(path) ??
271
+ new _CountingSet.default(),
272
+ path,
273
+ }),
254
274
  dependencies: new Map(previousDependencies),
255
275
  getSource: result.getSource,
256
276
  output: result.output,
277
+ unstable_transformResultKey: result.unstable_transformResultKey,
257
278
  };
258
- this.dependencies.set(module.path, module);
279
+
280
+ // Update the module information.
281
+ this.dependencies.set(nextModule.path, nextModule);
259
282
 
260
283
  // Diff dependencies (1/2): remove dependencies that have changed or been removed.
284
+ let dependenciesRemoved = false;
261
285
  for (const [key, prevDependency] of previousDependencies) {
262
286
  const curDependency = currentDependencies.get(key);
263
287
  if (
264
288
  !curDependency ||
265
289
  !dependenciesEqual(prevDependency, curDependency, options)
266
290
  ) {
267
- this._removeDependency(module, key, prevDependency, delta, options);
291
+ dependenciesRemoved = true;
292
+ this._removeDependency(nextModule, key, prevDependency, delta, options);
268
293
  }
269
294
  }
270
295
 
271
296
  // Diff dependencies (2/2): add dependencies that have changed or been added.
272
- const promises = [];
297
+ const addDependencyPromises = [];
273
298
  for (const [key, curDependency] of currentDependencies) {
274
299
  const prevDependency = previousDependencies.get(key);
275
300
  if (
276
301
  !prevDependency ||
277
302
  !dependenciesEqual(prevDependency, curDependency, options)
278
303
  ) {
279
- promises.push(
280
- this._addDependency(module, key, curDependency, delta, options)
304
+ addDependencyPromises.push(
305
+ this._addDependency(nextModule, key, curDependency, delta, options)
281
306
  );
282
307
  }
283
308
  }
284
- await Promise.all(promises);
309
+ if (
310
+ previousModule &&
311
+ !transfromOutputMayDiffer(previousModule, nextModule) &&
312
+ !dependenciesRemoved &&
313
+ addDependencyPromises.length === 0
314
+ ) {
315
+ // We have not operated on nextModule, so restore previousModule
316
+ // to aid diffing.
317
+ this.dependencies.set(previousModule.path, previousModule);
318
+ return previousModule;
319
+ }
320
+ delta.modified.add(path);
321
+ await Promise.all(addDependencyPromises);
285
322
 
286
323
  // Replace dependencies with the correctly-ordered version. As long as all
287
324
  // the above promises have resolved, this will be the same map but without
@@ -291,11 +328,11 @@ class Graph {
291
328
 
292
329
  // Catch obvious errors with a cheap assertion.
293
330
  invariant(
294
- module.dependencies.size === currentDependencies.size,
331
+ nextModule.dependencies.size === currentDependencies.size,
295
332
  "Failed to add the correct dependencies"
296
333
  );
297
- module.dependencies = currentDependencies;
298
- return module;
334
+ nextModule.dependencies = currentDependencies;
335
+ return nextModule;
299
336
  }
300
337
  async _addDependency(parentModule, key, dependency, delta, options) {
301
338
  const path = dependency.absolutePath;
@@ -686,6 +723,18 @@ function dependenciesEqual(a, b, options) {
686
723
  contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams))
687
724
  );
688
725
  }
726
+ function allDependenciesEqual(a, b, options) {
727
+ if (a.dependencies.size !== b.dependencies.size) {
728
+ return false;
729
+ }
730
+ for (const [key, depA] of a.dependencies) {
731
+ const depB = b.dependencies.get(key);
732
+ if (!depB || !dependenciesEqual(depA, depB, options)) {
733
+ return false;
734
+ }
735
+ }
736
+ return true;
737
+ }
689
738
  function contextParamsEqual(a, b) {
690
739
  return (
691
740
  a === b ||
@@ -698,3 +747,9 @@ function contextParamsEqual(a, b) {
698
747
  a.mode === b.mode)
699
748
  );
700
749
  }
750
+ function transfromOutputMayDiffer(a, b) {
751
+ return (
752
+ a.unstable_transformResultKey == null ||
753
+ a.unstable_transformResultKey !== b.unstable_transformResultKey
754
+ );
755
+ }
@@ -176,17 +176,24 @@ export class Graph<T = MixedOutput> {
176
176
 
177
177
  // Record the paths that are part of the dependency graph before we start
178
178
  // traversing - we'll use this to ensure we don't report modules modified
179
- // that only exist as part of the graph mid-traversal.
180
- const existingPaths = paths.filter(path => this.dependencies.has(path));
179
+ // that only exist as part of the graph mid-traversal, and to eliminate
180
+ // modules that end up in the same state that they started from the delta.
181
+ const originalModules = new Map<string, Module<T>>();
182
+ for (const path of paths) {
183
+ const originalModule = this.dependencies.get(path);
184
+ if (originalModule) {
185
+ originalModules.set(path, originalModule);
186
+ }
187
+ }
181
188
 
182
- for (const path of existingPaths) {
189
+ for (const [path] of originalModules) {
183
190
  // Traverse over modules that are part of the dependency graph.
184
191
  //
185
192
  // Note: A given path may not be part of the graph *at this time*, in
186
193
  // particular it may have been removed since we started traversing, but
187
194
  // in that case the path will be visited if and when we add it back to
188
195
  // the graph in a subsequent iteration.
189
- if (this.dependencies.get(path)) {
196
+ if (this.dependencies.has(path)) {
190
197
  await this._traverseDependenciesForSingleFile(
191
198
  path,
192
199
  delta,
@@ -208,17 +215,35 @@ export class Graph<T = MixedOutput> {
208
215
  // but may not actually differ, may be new, or may have been deleted after
209
216
  // processing. The actually-modified modules are the intersection of
210
217
  // delta.modified with the pre-existing paths, minus modules deleted.
211
- for (const path of existingPaths) {
218
+ for (const [path, originalModule] of originalModules) {
212
219
  invariant(
213
220
  !delta.added.has(path),
214
221
  'delta.added has %s, but this path was already in the graph.',
215
222
  path,
216
223
  );
217
224
  if (delta.modified.has(path)) {
218
- // Only report a module as modified if we're not already reporting it
219
- // as added or deleted.
225
+ // It's expected that a module may be both modified and subsequently
226
+ // deleted - we'll only return it as deleted.
220
227
  if (!delta.deleted.has(path)) {
221
- modified.set(path, nullthrows(this.dependencies.get(path)));
228
+ // If a module existed before and has not been deleted, it must be
229
+ // in the dependencies map.
230
+ const newModule = nullthrows(this.dependencies.get(path));
231
+ if (
232
+ // Module.dependencies is mutable, so it's not obviously the case
233
+ // that referential equality implies no modification. However, we
234
+ // only mutate dependencies in two cases:
235
+ // 1. Within _processModule. In that case, we always mutate a new
236
+ // module and set a new reference in this.dependencies.
237
+ // 2. During _releaseModule, when recursively removing
238
+ // dependencies. In that case, we immediately discard the module
239
+ // object.
240
+ // TODO: Refactor for more explicit immutability
241
+ newModule !== originalModule ||
242
+ transfromOutputMayDiffer(newModule, originalModule) ||
243
+ !allDependenciesEqual(newModule, originalModule)
244
+ ) {
245
+ modified.set(path, newModule);
246
+ }
222
247
  }
223
248
  }
224
249
  }
@@ -290,10 +315,6 @@ export class Graph<T = MixedOutput> {
290
315
  ): Promise<Module<T>> {
291
316
  const resolvedContext = this.#resolvedContexts.get(path);
292
317
 
293
- // Mark any module processed as potentially modified. Once we've finished
294
- // traversing we'll filter this set down.
295
- delta.modified.add(path);
296
-
297
318
  // Transform the file via the given option.
298
319
  // TODO: Unbind the transform method from options
299
320
  const result = await options.transform(path, resolvedContext);
@@ -306,48 +327,67 @@ export class Graph<T = MixedOutput> {
306
327
  options,
307
328
  );
308
329
 
309
- const previousModule = this.dependencies.get(path) || {
310
- inverseDependencies:
311
- delta.earlyInverseDependencies.get(path) || new CountingSet(),
312
- path,
313
- };
314
- const previousDependencies = previousModule.dependencies || new Map();
330
+ const previousModule = this.dependencies.get(path);
315
331
 
316
- // Update the module information.
317
- const module = {
318
- ...previousModule,
332
+ const previousDependencies = previousModule?.dependencies ?? new Map();
333
+
334
+ const nextModule = {
335
+ ...(previousModule ?? {
336
+ inverseDependencies:
337
+ delta.earlyInverseDependencies.get(path) ?? new CountingSet(),
338
+ path,
339
+ }),
319
340
  dependencies: new Map(previousDependencies),
320
341
  getSource: result.getSource,
321
342
  output: result.output,
343
+ unstable_transformResultKey: result.unstable_transformResultKey,
322
344
  };
323
- this.dependencies.set(module.path, module);
345
+
346
+ // Update the module information.
347
+ this.dependencies.set(nextModule.path, nextModule);
324
348
 
325
349
  // Diff dependencies (1/2): remove dependencies that have changed or been removed.
350
+ let dependenciesRemoved = false;
326
351
  for (const [key, prevDependency] of previousDependencies) {
327
352
  const curDependency = currentDependencies.get(key);
328
353
  if (
329
354
  !curDependency ||
330
355
  !dependenciesEqual(prevDependency, curDependency, options)
331
356
  ) {
332
- this._removeDependency(module, key, prevDependency, delta, options);
357
+ dependenciesRemoved = true;
358
+ this._removeDependency(nextModule, key, prevDependency, delta, options);
333
359
  }
334
360
  }
335
361
 
336
362
  // Diff dependencies (2/2): add dependencies that have changed or been added.
337
- const promises = [];
363
+ const addDependencyPromises = [];
338
364
  for (const [key, curDependency] of currentDependencies) {
339
365
  const prevDependency = previousDependencies.get(key);
340
366
  if (
341
367
  !prevDependency ||
342
368
  !dependenciesEqual(prevDependency, curDependency, options)
343
369
  ) {
344
- promises.push(
345
- this._addDependency(module, key, curDependency, delta, options),
370
+ addDependencyPromises.push(
371
+ this._addDependency(nextModule, key, curDependency, delta, options),
346
372
  );
347
373
  }
348
374
  }
349
375
 
350
- await Promise.all(promises);
376
+ if (
377
+ previousModule &&
378
+ !transfromOutputMayDiffer(previousModule, nextModule) &&
379
+ !dependenciesRemoved &&
380
+ addDependencyPromises.length === 0
381
+ ) {
382
+ // We have not operated on nextModule, so restore previousModule
383
+ // to aid diffing.
384
+ this.dependencies.set(previousModule.path, previousModule);
385
+ return previousModule;
386
+ }
387
+
388
+ delta.modified.add(path);
389
+
390
+ await Promise.all(addDependencyPromises);
351
391
 
352
392
  // Replace dependencies with the correctly-ordered version. As long as all
353
393
  // the above promises have resolved, this will be the same map but without
@@ -357,13 +397,13 @@ export class Graph<T = MixedOutput> {
357
397
 
358
398
  // Catch obvious errors with a cheap assertion.
359
399
  invariant(
360
- module.dependencies.size === currentDependencies.size,
400
+ nextModule.dependencies.size === currentDependencies.size,
361
401
  'Failed to add the correct dependencies',
362
402
  );
363
403
 
364
- module.dependencies = currentDependencies;
404
+ nextModule.dependencies = currentDependencies;
365
405
 
366
- return module;
406
+ return nextModule;
367
407
  }
368
408
 
369
409
  async _addDependency(
@@ -470,7 +510,10 @@ export class Graph<T = MixedOutput> {
470
510
  /**
471
511
  * Collect a list of context modules which include a given file.
472
512
  */
473
- markModifiedContextModules(filePath: string, modifiedPaths: Set<string>) {
513
+ markModifiedContextModules(
514
+ filePath: string,
515
+ modifiedPaths: Set<string> | CountingSet<string>,
516
+ ) {
474
517
  for (const [absolutePath, context] of this.#resolvedContexts) {
475
518
  if (
476
519
  !modifiedPaths.has(absolutePath) &&
@@ -814,6 +857,23 @@ function dependenciesEqual(
814
857
  );
815
858
  }
816
859
 
860
+ function allDependenciesEqual<T>(
861
+ a: Module<T>,
862
+ b: Module<T>,
863
+ options: $ReadOnly<{lazy: boolean, ...}>,
864
+ ): boolean {
865
+ if (a.dependencies.size !== b.dependencies.size) {
866
+ return false;
867
+ }
868
+ for (const [key, depA] of a.dependencies) {
869
+ const depB = b.dependencies.get(key);
870
+ if (!depB || !dependenciesEqual(depA, depB, options)) {
871
+ return false;
872
+ }
873
+ }
874
+ return true;
875
+ }
876
+
817
877
  function contextParamsEqual(
818
878
  a: ?RequireContextParams,
819
879
  b: ?RequireContextParams,
@@ -829,3 +889,10 @@ function contextParamsEqual(
829
889
  a.mode === b.mode)
830
890
  );
831
891
  }
892
+
893
+ function transfromOutputMayDiffer<T>(a: Module<T>, b: Module<T>): boolean {
894
+ return (
895
+ a.unstable_transformResultKey == null ||
896
+ a.unstable_transformResultKey !== b.unstable_transformResultKey
897
+ );
898
+ }
@@ -12,6 +12,7 @@
12
12
  "use strict";
13
13
 
14
14
  const invariant = require("invariant");
15
+ const jscSafeUrl = require("jsc-safe-url");
15
16
  const { addParamsToDefineCall } = require("metro-transform-plugins");
16
17
  const path = require("path");
17
18
  function wrapModule(module, options) {
@@ -41,7 +42,9 @@ function getModuleParams(module, options) {
41
42
  // Construct a server-relative URL for the split bundle, propagating
42
43
  // most parameters from the main bundle's URL.
43
44
 
44
- const { searchParams } = new URL(options.sourceUrl);
45
+ const { searchParams } = new URL(
46
+ jscSafeUrl.toNormalUrl(options.sourceUrl)
47
+ );
45
48
  searchParams.set("modulesOnly", "true");
46
49
  searchParams.set("runModule", "false");
47
50
  const bundlePath = path.relative(
@@ -15,6 +15,7 @@ import type {MixedOutput, Module} from '../../types.flow';
15
15
  import type {JsOutput} from 'metro-transform-worker';
16
16
 
17
17
  const invariant = require('invariant');
18
+ const jscSafeUrl = require('jsc-safe-url');
18
19
  const {addParamsToDefineCall} = require('metro-transform-plugins');
19
20
  const path = require('path');
20
21
 
@@ -59,7 +60,9 @@ function getModuleParams(module: Module<>, options: Options): Array<mixed> {
59
60
  // Construct a server-relative URL for the split bundle, propagating
60
61
  // most parameters from the main bundle's URL.
61
62
 
62
- const {searchParams} = new URL(options.sourceUrl);
63
+ const {searchParams} = new URL(
64
+ jscSafeUrl.toNormalUrl(options.sourceUrl),
65
+ );
63
66
  searchParams.set('modulesOnly', 'true');
64
67
  searchParams.set('runModule', 'false');
65
68
 
@@ -12,6 +12,7 @@
12
12
  "use strict";
13
13
 
14
14
  const { isJsModule, wrapModule } = require("./helpers/js");
15
+ const jscSafeUrl = require("jsc-safe-url");
15
16
  const { addParamsToDefineCall } = require("metro-transform-plugins");
16
17
  const path = require("path");
17
18
  const url = require("url");
@@ -33,7 +34,7 @@ function generateModules(sourceModules, graph, options) {
33
34
  return url.format(options.clientUrl);
34
35
  };
35
36
  const sourceMappingURL = getURL("map");
36
- const sourceURL = getURL("bundle");
37
+ const sourceURL = jscSafeUrl.toJscSafeUrl(getURL("bundle"));
37
38
  const code =
38
39
  prepareModule(module, graph, options) +
39
40
  `\n//# sourceMappingURL=${sourceMappingURL}\n` +
@@ -16,6 +16,7 @@ import type {DeltaResult, Module, ReadOnlyGraph} from '../types.flow';
16
16
  import type {HmrModule} from 'metro-runtime/src/modules/types.flow';
17
17
 
18
18
  const {isJsModule, wrapModule} = require('./helpers/js');
19
+ const jscSafeUrl = require('jsc-safe-url');
19
20
  const {addParamsToDefineCall} = require('metro-transform-plugins');
20
21
  const path = require('path');
21
22
  const url = require('url');
@@ -53,7 +54,7 @@ function generateModules(
53
54
  };
54
55
 
55
56
  const sourceMappingURL = getURL('map');
56
- const sourceURL = getURL('bundle');
57
+ const sourceURL = jscSafeUrl.toJscSafeUrl(getURL('bundle'));
57
58
  const code =
58
59
  prepareModule(module, graph, options) +
59
60
  `\n//# sourceMappingURL=${sourceMappingURL}\n` +
@@ -130,6 +130,7 @@ class Transformer {
130
130
  cache.set(fullKey, data.result);
131
131
  return {
132
132
  ...data.result,
133
+ unstable_transformResultKey: fullKey.toString(),
133
134
  getSource() {
134
135
  if (fileBuffer) {
135
136
  return fileBuffer;
@@ -155,6 +155,7 @@ class Transformer {
155
155
 
156
156
  return {
157
157
  ...data.result,
158
+ unstable_transformResultKey: fullKey.toString(),
158
159
  getSource(): Buffer {
159
160
  if (fileBuffer) {
160
161
  return fileBuffer;
@@ -61,13 +61,14 @@ export type Dependency = {
61
61
  +data: TransformResultDependency,
62
62
  };
63
63
 
64
- export type Module<T = MixedOutput> = {
65
- +dependencies: Map<string, Dependency>,
66
- +inverseDependencies: CountingSet<string>,
67
- +output: $ReadOnlyArray<T>,
68
- +path: string,
69
- +getSource: () => Buffer,
70
- };
64
+ export type Module<T = MixedOutput> = $ReadOnly<{
65
+ dependencies: Map<string, Dependency>,
66
+ inverseDependencies: CountingSet<string>,
67
+ output: $ReadOnlyArray<T>,
68
+ path: string,
69
+ getSource: () => Buffer,
70
+ unstable_transformResultKey?: ?string,
71
+ }>;
71
72
 
72
73
  export type Dependencies<T = MixedOutput> = Map<string, Module<T>>;
73
74
  export type ReadOnlyDependencies<T = MixedOutput> = $ReadOnlyMap<
@@ -102,6 +103,7 @@ export type {Graph};
102
103
  export type TransformResult<T = MixedOutput> = $ReadOnly<{
103
104
  dependencies: $ReadOnlyArray<TransformResultDependency>,
104
105
  output: $ReadOnlyArray<T>,
106
+ unstable_transformResultKey?: ?string,
105
107
  }>;
106
108
 
107
109
  export type TransformResultWithSource<T = MixedOutput> = $ReadOnly<{
package/src/Server.js CHANGED
@@ -34,6 +34,8 @@ const { codeFrameColumns } = require("@babel/code-frame");
34
34
  const MultipartResponse = require("./Server/MultipartResponse");
35
35
  const debug = require("debug")("Metro:Server");
36
36
  const fs = require("graceful-fs");
37
+ const invariant = require("invariant");
38
+ const jscSafeUrl = require("jsc-safe-url");
37
39
  const {
38
40
  Logger,
39
41
  Logger: { createActionStartEntry, createActionEndEntry, log },
@@ -355,9 +357,14 @@ class Server {
355
357
  _parseOptions(url) {
356
358
  return parseOptionsFromUrl(url, new Set(this._config.resolver.platforms));
357
359
  }
360
+ _rewriteAndNormalizeUrl(requestUrl) {
361
+ return jscSafeUrl.toNormalUrl(
362
+ this._config.server.rewriteRequestUrl(jscSafeUrl.toNormalUrl(requestUrl))
363
+ );
364
+ }
358
365
  async _processRequest(req, res, next) {
359
366
  const originalUrl = req.url;
360
- req.url = this._config.server.rewriteRequestUrl(req.url);
367
+ req.url = this._rewriteAndNormalizeUrl(req.url);
361
368
  const urlObj = url.parse(req.url, true);
362
369
  const { host } = req.headers;
363
370
  debug(
@@ -704,7 +711,7 @@ class Server {
704
711
  bundle: bundleCode,
705
712
  };
706
713
  },
707
- finish({ req, mres, result }) {
714
+ finish({ req, mres, serializerOptions, result }) {
708
715
  if (
709
716
  // We avoid parsing the dates since the client should never send a more
710
717
  // recent date than the one returned by the Delta Bundler (if that's the
@@ -721,6 +728,9 @@ class Server {
721
728
  String(result.numModifiedFiles)
722
729
  );
723
730
  mres.setHeader(DELTA_ID_HEADER, String(result.nextRevId));
731
+ if (serializerOptions?.sourceUrl != null) {
732
+ mres.setHeader("Content-Location", serializerOptions.sourceUrl);
733
+ }
724
734
  mres.setHeader("Content-Type", "application/javascript; charset=UTF-8");
725
735
  mres.setHeader("Last-Modified", result.lastModifiedDate.toUTCString());
726
736
  mres.setHeader(
@@ -899,18 +909,27 @@ class Server {
899
909
  /* $FlowFixMe: where is `rawBody` defined? Is it added by the `connect` framework? */
900
910
  const body = await req.rawBody;
901
911
  const parsedBody = JSON.parse(body);
902
- const stack = parsedBody.stack.map((frame) => {
903
- if (frame.file && frame.file.includes("://")) {
912
+ const rewriteAndNormalizeStackFrame = (frame, lineNumber) => {
913
+ invariant(
914
+ frame != null && typeof frame === "object",
915
+ "Bad stack frame at line %d, expected object, received: %s",
916
+ lineNumber,
917
+ typeof frame
918
+ );
919
+ const frameFile = frame.file;
920
+ if (typeof frameFile === "string" && frameFile.includes("://")) {
904
921
  return {
905
922
  ...frame,
906
- file: this._config.server.rewriteRequestUrl(frame.file),
923
+ file: this._rewriteAndNormalizeUrl(frameFile),
907
924
  };
908
925
  }
909
926
  return frame;
910
- });
927
+ };
928
+ const stack = parsedBody.stack.map(rewriteAndNormalizeStackFrame);
911
929
  // In case of multiple bundles / HMR, some stack frames can have different URLs from others
912
930
  const urls = new Set();
913
931
  stack.forEach((frame) => {
932
+ // These urls have been rewritten and normalized above.
914
933
  const sourceUrl = frame.file;
915
934
  // Skip `/debuggerWorker.js` which does not need symbolication.
916
935
  if (
@@ -924,8 +943,11 @@ class Server {
924
943
  });
925
944
  debug("Getting source maps for symbolication");
926
945
  const sourceMaps = await Promise.all(
927
- // $FlowFixMe[method-unbinding] added when improving typing for this parameters
928
- Array.from(urls.values()).map(this._explodedSourceMapForURL, this)
946
+ Array.from(urls.values()).map((normalizedUrl) =>
947
+ this._explodedSourceMapForBundleOptions(
948
+ this._parseOptions(normalizedUrl)
949
+ )
950
+ )
929
951
  );
930
952
  debug("Performing fast symbolication");
931
953
  const symbolicatedStack = await symbolicate(
@@ -954,11 +976,7 @@ class Server {
954
976
  );
955
977
  }
956
978
  }
957
- async _explodedSourceMapForURL(reqUrl) {
958
- const options = parseOptionsFromUrl(
959
- reqUrl,
960
- new Set(this._config.resolver.platforms)
961
- );
979
+ async _explodedSourceMapForBundleOptions(bundleOptions) {
962
980
  const {
963
981
  entryFile,
964
982
  graphOptions,
@@ -966,7 +984,7 @@ class Server {
966
984
  resolverOptions,
967
985
  serializerOptions,
968
986
  transformOptions,
969
- } = splitBundleOptions(options);
987
+ } = splitBundleOptions(bundleOptions);
970
988
 
971
989
  /**
972
990
  * `entryFile` is relative to projectRoot, we need to use resolution function