metro 0.70.2 → 0.71.1

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.
Files changed (79) hide show
  1. package/package.json +22 -21
  2. package/src/Assets.js.flow +4 -4
  3. package/src/Bundler/util.js +1 -1
  4. package/src/Bundler/util.js.flow +2 -2
  5. package/src/Bundler.js +17 -10
  6. package/src/Bundler.js.flow +19 -14
  7. package/src/DeltaBundler/DeltaCalculator.js +13 -17
  8. package/src/DeltaBundler/DeltaCalculator.js.flow +15 -20
  9. package/src/DeltaBundler/Serializers/getAllFiles.js.flow +2 -2
  10. package/src/DeltaBundler/Serializers/getAssets.js.flow +2 -2
  11. package/src/DeltaBundler/Serializers/getExplodedSourceMap.js.flow +4 -4
  12. package/src/DeltaBundler/Serializers/getRamBundleInfo.js.flow +6 -6
  13. package/src/DeltaBundler/Serializers/helpers/getSourceMapInfo.js.flow +4 -4
  14. package/src/DeltaBundler/Serializers/helpers/processBytecodeModules.js.flow +2 -2
  15. package/src/DeltaBundler/Serializers/helpers/processModules.js.flow +2 -2
  16. package/src/DeltaBundler/Serializers/hmrJSBundle.js.flow +2 -2
  17. package/src/DeltaBundler/Serializers/sourceMapGenerator.js.flow +6 -6
  18. package/src/DeltaBundler/Serializers/sourceMapObject.js.flow +4 -4
  19. package/src/DeltaBundler/Serializers/sourceMapString.js.flow +2 -2
  20. package/src/DeltaBundler/Worker.flow.js +78 -0
  21. package/src/DeltaBundler/Worker.flow.js.flow +121 -0
  22. package/src/DeltaBundler/Worker.js +8 -66
  23. package/src/DeltaBundler/Worker.js.flow +8 -107
  24. package/src/DeltaBundler/WorkerFarm.js.flow +4 -4
  25. package/src/DeltaBundler/__fixtures__/hasteImpl.js +4 -0
  26. package/src/DeltaBundler/getTransformCacheKey.js.flow +2 -2
  27. package/src/DeltaBundler/graphOperations.js +634 -0
  28. package/src/DeltaBundler/graphOperations.js.flow +749 -0
  29. package/src/DeltaBundler/types.flow.js.flow +36 -30
  30. package/src/DeltaBundler.js +14 -6
  31. package/src/DeltaBundler.js.flow +14 -10
  32. package/src/HmrServer.js.flow +6 -6
  33. package/src/IncrementalBundler.js +1 -1
  34. package/src/IncrementalBundler.js.flow +8 -8
  35. package/src/ModuleGraph/node-haste/ModuleCache.js +1 -1
  36. package/src/ModuleGraph/node-haste/ModuleCache.js.flow +1 -1
  37. package/src/ModuleGraph/node-haste/node-haste.flow.js.flow +2 -2
  38. package/src/ModuleGraph/node-haste/node-haste.js +4 -4
  39. package/src/ModuleGraph/node-haste/node-haste.js.flow +13 -7
  40. package/src/ModuleGraph/output/indexed-ram-bundle.js.flow +2 -2
  41. package/src/ModuleGraph/output/plain-bundle.js.flow +2 -2
  42. package/src/ModuleGraph/output/reverse-dependency-map-references.js.flow +8 -8
  43. package/src/ModuleGraph/output/util.js.flow +2 -2
  44. package/src/ModuleGraph/types.flow.js.flow +37 -37
  45. package/src/ModuleGraph/worker/collectDependencies.js.flow +2 -2
  46. package/src/Server/symbolicate.js.flow +1 -1
  47. package/src/Server.js.flow +18 -18
  48. package/src/cli.js +5 -0
  49. package/src/cli.js.flow +5 -0
  50. package/src/commands/build.js +4 -3
  51. package/src/commands/build.js.flow +5 -3
  52. package/src/commands/serve.js +3 -3
  53. package/src/commands/serve.js.flow +5 -3
  54. package/src/index.flow.js +392 -0
  55. package/src/index.flow.js.flow +480 -0
  56. package/src/index.js +8 -366
  57. package/src/index.js.flow +8 -456
  58. package/src/lib/bundleToBytecode.js.flow +2 -2
  59. package/src/lib/bundleToString.js.flow +2 -2
  60. package/src/lib/getPreludeCode.js.flow +2 -2
  61. package/src/lib/transformHelpers.js.flow +2 -2
  62. package/src/node-haste/DependencyGraph/ModuleResolution.js +17 -4
  63. package/src/node-haste/DependencyGraph/ModuleResolution.js.flow +20 -12
  64. package/src/node-haste/DependencyGraph/createHasteMap.js +79 -19
  65. package/src/node-haste/DependencyGraph/createHasteMap.js.flow +15 -14
  66. package/src/node-haste/DependencyGraph.js +31 -27
  67. package/src/node-haste/DependencyGraph.js.flow +43 -37
  68. package/src/node-haste/ModuleCache.js.flow +1 -1
  69. package/src/node-haste/lib/AssetPaths.js.flow +2 -2
  70. package/src/node-haste/lib/parsePlatformFilePath.js.flow +2 -2
  71. package/src/shared/output/RamBundle/as-indexed-file.js.flow +1 -1
  72. package/src/shared/output/RamBundle/buildSourcemapWithMetadata.js.flow +2 -2
  73. package/src/shared/types.flow.js.flow +14 -14
  74. package/src/DeltaBundler/computeDelta.js +0 -42
  75. package/src/DeltaBundler/computeDelta.js.flow +0 -47
  76. package/src/DeltaBundler/traverseDependencies.js +0 -470
  77. package/src/DeltaBundler/traverseDependencies.js.flow +0 -565
  78. package/src/node-haste/DependencyGraph/types.js +0 -10
  79. package/src/node-haste/DependencyGraph/types.js.flow +0 -88
@@ -0,0 +1,749 @@
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-local
8
+ * @format
9
+ */
10
+
11
+ /**
12
+ * Portions of this code are based on the Synchronous Cycle Collection
13
+ * algorithm described in:
14
+ *
15
+ * David F. Bacon and V. T. Rajan. 2001. Concurrent Cycle Collection in
16
+ * Reference Counted Systems. In Proceedings of the 15th European Conference on
17
+ * Object-Oriented Programming (ECOOP '01). Springer-Verlag, Berlin,
18
+ * Heidelberg, 207–235.
19
+ *
20
+ * Notable differences from the algorithm in the paper:
21
+ * 1. Our implementation uses the inverseDependencies set (which we already
22
+ * have to maintain) instead of a separate refcount variable. A module's
23
+ * reference count is equal to the size of its inverseDependencies set, plus
24
+ * 1 if it's an entry point of the graph.
25
+ * 2. We keep the "root buffer" (possibleCycleRoots) free of duplicates by
26
+ * making it a Set, instead of storing a "buffered" flag on each node.
27
+ * 3. On top of tracking edges between nodes, we also count references between
28
+ * nodes and entries in the importBundleNames set.
29
+ */
30
+
31
+ 'use strict';
32
+
33
+ import type {
34
+ Dependency,
35
+ Graph,
36
+ GraphInputOptions,
37
+ Module,
38
+ Options,
39
+ TransformResultDependency,
40
+ } from './types.flow';
41
+
42
+ const invariant = require('invariant');
43
+ const nullthrows = require('nullthrows');
44
+
45
+ // TODO: Convert to a Flow enum
46
+ type NodeColor =
47
+ // In use or free
48
+ | 'black'
49
+
50
+ // Possible member of cycle
51
+ | 'gray'
52
+
53
+ // Member of garbage cycle
54
+ | 'white'
55
+
56
+ // Possible root of cycle
57
+ | 'purple'
58
+
59
+ // Inherently acyclic node (Not currently used)
60
+ | 'green';
61
+
62
+ // Private state for the graph that persists between operations.
63
+ export opaque type PrivateState = {
64
+ +gc: {
65
+ // GC state for nodes in the graph (graph.dependencies)
66
+ +color: Map<string, NodeColor>,
67
+ +possibleCycleRoots: Set<string>,
68
+
69
+ // Reference counts for entries in importBundleNames
70
+ +importBundleRefs: Map<string, number>,
71
+ },
72
+ };
73
+
74
+ function createGraph<T>(options: GraphInputOptions): Graph<T> {
75
+ return {
76
+ ...options,
77
+ dependencies: new Map(),
78
+ importBundleNames: new Set(),
79
+ privateState: {
80
+ gc: {
81
+ color: new Map(),
82
+ possibleCycleRoots: new Set(),
83
+ importBundleRefs: new Map(),
84
+ },
85
+ },
86
+ };
87
+ }
88
+
89
+ type Result<T> = {
90
+ added: Map<string, Module<T>>,
91
+ modified: Map<string, Module<T>>,
92
+ deleted: Set<string>,
93
+ };
94
+
95
+ /**
96
+ * Internal data structure that the traversal logic uses to know which of the
97
+ * files have been modified. This allows to return the added modules before the
98
+ * modified ones (which is useful for things like Hot Module Reloading).
99
+ **/
100
+ type Delta = $ReadOnly<{
101
+ added: Set<string>,
102
+ modified: Set<string>,
103
+ deleted: Set<string>,
104
+
105
+ // A place to temporarily track inverse dependencies for a module while it is
106
+ // being processed but has not been added to `graph.dependencies` yet.
107
+ earlyInverseDependencies: Map<string, Set<string>>,
108
+ }>;
109
+
110
+ type InternalOptions<T> = $ReadOnly<{
111
+ experimentalImportBundleSupport: boolean,
112
+ onDependencyAdd: () => mixed,
113
+ onDependencyAdded: () => mixed,
114
+ resolve: Options<T>['resolve'],
115
+ transform: Options<T>['transform'],
116
+ shallow: boolean,
117
+ }>;
118
+
119
+ function getInternalOptions<T>({
120
+ transform,
121
+ resolve,
122
+ onProgress,
123
+ experimentalImportBundleSupport,
124
+ shallow,
125
+ }: Options<T>): InternalOptions<T> {
126
+ let numProcessed = 0;
127
+ let total = 0;
128
+
129
+ return {
130
+ experimentalImportBundleSupport,
131
+ transform,
132
+ resolve,
133
+ onDependencyAdd: () => onProgress && onProgress(numProcessed, ++total),
134
+ onDependencyAdded: () => onProgress && onProgress(++numProcessed, total),
135
+ shallow,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Dependency Traversal logic for the Delta Bundler. This method calculates
141
+ * the modules that should be included in the bundle by traversing the
142
+ * dependency graph.
143
+ * Instead of traversing the whole graph each time, it just calculates the
144
+ * difference between runs by only traversing the added/removed dependencies.
145
+ * To do so, it uses the passed passed graph dependencies and it mutates it.
146
+ * The paths parameter contains the absolute paths of the root files that the
147
+ * method should traverse. Normally, these paths should be the modified files
148
+ * since the last traversal.
149
+ */
150
+ async function traverseDependencies<T>(
151
+ paths: $ReadOnlyArray<string>,
152
+ graph: Graph<T>,
153
+ options: Options<T>,
154
+ ): Promise<Result<T>> {
155
+ const delta = {
156
+ added: new Set(),
157
+ modified: new Set(),
158
+ deleted: new Set(),
159
+ earlyInverseDependencies: new Map(),
160
+ };
161
+
162
+ const internalOptions = getInternalOptions(options);
163
+
164
+ for (const path of paths) {
165
+ // Start traversing from modules that are already part of the dependency graph.
166
+ if (graph.dependencies.get(path)) {
167
+ delta.modified.add(path);
168
+
169
+ await traverseDependenciesForSingleFile(
170
+ path,
171
+ graph,
172
+ delta,
173
+ internalOptions,
174
+ );
175
+ }
176
+ }
177
+
178
+ collectCycles(graph, delta);
179
+
180
+ const added = new Map();
181
+ for (const path of delta.added) {
182
+ added.set(path, nullthrows(graph.dependencies.get(path)));
183
+ }
184
+
185
+ const modified = new Map();
186
+ for (const path of delta.modified) {
187
+ // Only report a module as modified if we're not already reporting it as added.
188
+ if (!delta.added.has(path)) {
189
+ modified.set(path, nullthrows(graph.dependencies.get(path)));
190
+ }
191
+ }
192
+
193
+ return {
194
+ added,
195
+ modified,
196
+ deleted: delta.deleted,
197
+ };
198
+ }
199
+
200
+ async function initialTraverseDependencies<T>(
201
+ graph: Graph<T>,
202
+ options: Options<T>,
203
+ ): Promise<Result<T>> {
204
+ const delta = {
205
+ added: new Set(),
206
+ modified: new Set(),
207
+ deleted: new Set(),
208
+ earlyInverseDependencies: new Map(),
209
+ };
210
+
211
+ const internalOptions = getInternalOptions(options);
212
+
213
+ invariant(
214
+ graph.dependencies.size === 0,
215
+ 'initialTraverseDependencies called on nonempty graph',
216
+ );
217
+ invariant(
218
+ graph.importBundleNames.size === 0,
219
+ 'initialTraverseDependencies called on nonempty graph',
220
+ );
221
+
222
+ graph.privateState.gc.color.clear();
223
+ graph.privateState.gc.possibleCycleRoots.clear();
224
+ graph.privateState.gc.importBundleRefs.clear();
225
+
226
+ for (const path of graph.entryPoints) {
227
+ // Each entry point implicitly has a refcount of 1, so mark them all black.
228
+ graph.privateState.gc.color.set(path, 'black');
229
+ }
230
+
231
+ await Promise.all(
232
+ [...graph.entryPoints].map((path: string) =>
233
+ traverseDependenciesForSingleFile(path, graph, delta, internalOptions),
234
+ ),
235
+ );
236
+
237
+ reorderGraph(graph, {
238
+ shallow: options.shallow,
239
+ });
240
+
241
+ return {
242
+ added: graph.dependencies,
243
+ modified: new Map(),
244
+ deleted: new Set(),
245
+ };
246
+ }
247
+
248
+ async function traverseDependenciesForSingleFile<T>(
249
+ path: string,
250
+ graph: Graph<T>,
251
+ delta: Delta,
252
+ options: InternalOptions<T>,
253
+ ): Promise<void> {
254
+ options.onDependencyAdd();
255
+
256
+ await processModule(path, graph, delta, options);
257
+
258
+ options.onDependencyAdded();
259
+ }
260
+
261
+ async function processModule<T>(
262
+ path: string,
263
+ graph: Graph<T>,
264
+ delta: Delta,
265
+ options: InternalOptions<T>,
266
+ ): Promise<Module<T>> {
267
+ // Transform the file via the given option.
268
+ // TODO: Unbind the transform method from options
269
+ const result = await options.transform(path);
270
+
271
+ // Get the absolute path of all sub-dependencies (some of them could have been
272
+ // moved but maintain the same relative path).
273
+ const currentDependencies = resolveDependencies(
274
+ path,
275
+ result.dependencies,
276
+ options,
277
+ );
278
+
279
+ const previousModule = graph.dependencies.get(path) || {
280
+ inverseDependencies: delta.earlyInverseDependencies.get(path) || new Set(),
281
+ path,
282
+ };
283
+ const previousDependencies = previousModule.dependencies || new Map();
284
+
285
+ // Update the module information.
286
+ const module = {
287
+ ...previousModule,
288
+ dependencies: new Map(previousDependencies),
289
+ getSource: result.getSource,
290
+ output: result.output,
291
+ };
292
+ graph.dependencies.set(module.path, module);
293
+
294
+ // Diff dependencies (1/2): remove dependencies that have changed or been removed.
295
+ for (const [relativePath, prevDependency] of previousDependencies) {
296
+ const curDependency = currentDependencies.get(relativePath);
297
+ if (
298
+ !curDependency ||
299
+ curDependency.absolutePath !== prevDependency.absolutePath ||
300
+ (options.experimentalImportBundleSupport &&
301
+ curDependency.data.data.asyncType !==
302
+ prevDependency.data.data.asyncType)
303
+ ) {
304
+ removeDependency(
305
+ module,
306
+ relativePath,
307
+ prevDependency,
308
+ graph,
309
+ delta,
310
+ options,
311
+ );
312
+ }
313
+ }
314
+
315
+ // Diff dependencies (2/2): add dependencies that have changed or been added.
316
+ const promises = [];
317
+ for (const [relativePath, curDependency] of currentDependencies) {
318
+ const prevDependency = previousDependencies.get(relativePath);
319
+ if (
320
+ !prevDependency ||
321
+ prevDependency.absolutePath !== curDependency.absolutePath ||
322
+ (options.experimentalImportBundleSupport &&
323
+ prevDependency.data.data.asyncType !==
324
+ curDependency.data.data.asyncType)
325
+ ) {
326
+ promises.push(
327
+ addDependency(
328
+ module,
329
+ relativePath,
330
+ curDependency,
331
+ graph,
332
+ delta,
333
+ options,
334
+ ),
335
+ );
336
+ }
337
+ }
338
+
339
+ await Promise.all(promises);
340
+
341
+ // Replace dependencies with the correctly-ordered version. As long as all
342
+ // the above promises have resolved, this will be the same map but without
343
+ // the added nondeterminism of promise resolution order. Because this
344
+ // assignment does not add or remove edges, it does NOT invalidate any of the
345
+ // garbage collection state.
346
+
347
+ // Catch obvious errors with a cheap assertion.
348
+ invariant(
349
+ module.dependencies.size === currentDependencies.size,
350
+ 'Failed to add the correct dependencies',
351
+ );
352
+
353
+ // $FlowFixMe[cannot-write]
354
+ module.dependencies = currentDependencies;
355
+
356
+ return module;
357
+ }
358
+
359
+ async function addDependency<T>(
360
+ parentModule: Module<T>,
361
+ relativePath: string,
362
+ dependency: Dependency,
363
+ graph: Graph<T>,
364
+ delta: Delta,
365
+ options: InternalOptions<T>,
366
+ ): Promise<void> {
367
+ const path = dependency.absolutePath;
368
+
369
+ // The module may already exist, in which case we just need to update some
370
+ // bookkeeping instead of adding a new node to the graph.
371
+ let module = graph.dependencies.get(path);
372
+
373
+ if (options.shallow) {
374
+ // Don't add a node for the module if the graph is shallow (single-module).
375
+ } else if (
376
+ options.experimentalImportBundleSupport &&
377
+ dependency.data.data.asyncType != null
378
+ ) {
379
+ // Don't add a node for the module if we are traversing async dependencies
380
+ // lazily (and this is an async dependency). Instead, record it in
381
+ // importBundleNames.
382
+ incrementImportBundleReference(dependency, graph);
383
+ } else {
384
+ if (!module) {
385
+ // Add a new node to the graph.
386
+ const earlyInverseDependencies = delta.earlyInverseDependencies.get(path);
387
+ if (earlyInverseDependencies) {
388
+ // This module is being transformed at the moment in parallel, so we
389
+ // should only mark its parent as an inverse dependency.
390
+ earlyInverseDependencies.add(parentModule.path);
391
+ } else {
392
+ if (delta.deleted.has(path)) {
393
+ // Mark the addition by clearing a prior deletion.
394
+ delta.deleted.delete(path);
395
+ } else {
396
+ // Mark the addition in the added set.
397
+ delta.added.add(path);
398
+ delta.modified.delete(path);
399
+ }
400
+ delta.earlyInverseDependencies.set(path, new Set([parentModule.path]));
401
+
402
+ options.onDependencyAdd();
403
+ module = await processModule(path, graph, delta, options);
404
+ options.onDependencyAdded();
405
+
406
+ graph.dependencies.set(module.path, module);
407
+ }
408
+ }
409
+ if (module) {
410
+ // We either added a new node to the graph, or we're updating an existing one.
411
+ module.inverseDependencies.add(parentModule.path);
412
+ markModuleInUse(module, graph);
413
+ }
414
+ }
415
+
416
+ // Always update the parent's dependency map.
417
+ // This means the parent's dependencies can get desynced from
418
+ // inverseDependencies and the other fields in the case of lazy edges.
419
+ // Not an optimal representation :(
420
+ parentModule.dependencies.set(relativePath, dependency);
421
+ }
422
+
423
+ function removeDependency<T>(
424
+ parentModule: Module<T>,
425
+ relativePath: string,
426
+ dependency: Dependency,
427
+ graph: Graph<T>,
428
+ delta: Delta,
429
+ options: InternalOptions<T>,
430
+ ): void {
431
+ parentModule.dependencies.delete(relativePath);
432
+
433
+ const {absolutePath} = dependency;
434
+
435
+ if (
436
+ options.experimentalImportBundleSupport &&
437
+ dependency.data.data.asyncType != null
438
+ ) {
439
+ decrementImportBundleReference(dependency, graph);
440
+ }
441
+
442
+ const module = graph.dependencies.get(absolutePath);
443
+
444
+ if (!module) {
445
+ return;
446
+ }
447
+ module.inverseDependencies.delete(parentModule.path);
448
+ if (
449
+ module.inverseDependencies.size > 0 ||
450
+ graph.entryPoints.has(absolutePath)
451
+ ) {
452
+ // The reference count has decreased, but not to zero.
453
+ // NOTE: Each entry point implicitly has a refcount of 1.
454
+ markAsPossibleCycleRoot(module, graph);
455
+ } else {
456
+ // The reference count has decreased to zero.
457
+ releaseModule(module, graph, delta, options);
458
+ }
459
+ }
460
+
461
+ function resolveDependencies<T>(
462
+ parentPath: string,
463
+ dependencies: $ReadOnlyArray<TransformResultDependency>,
464
+ options: InternalOptions<T>,
465
+ ): Map<string, Dependency> {
466
+ const resolve = (parentPath: string, result: TransformResultDependency) => {
467
+ const relativePath = result.name;
468
+ try {
469
+ return [
470
+ relativePath,
471
+ {
472
+ absolutePath: options.resolve(parentPath, relativePath),
473
+ data: result,
474
+ },
475
+ ];
476
+ } catch (error) {
477
+ // Ignore unavailable optional dependencies. They are guarded
478
+ // with a try-catch block and will be handled during runtime.
479
+ if (result.data.isOptional !== true) {
480
+ throw error;
481
+ }
482
+ }
483
+ return undefined;
484
+ };
485
+
486
+ const resolved = dependencies.reduce(
487
+ (list: Array<[string, Dependency]>, result: TransformResultDependency) => {
488
+ const resolvedPath = resolve(parentPath, result);
489
+ if (resolvedPath) {
490
+ list.push(resolvedPath);
491
+ }
492
+ return list;
493
+ },
494
+ [],
495
+ );
496
+ return new Map(resolved);
497
+ }
498
+
499
+ /**
500
+ * Re-traverse the dependency graph in DFS order to reorder the modules and
501
+ * guarantee the same order between runs. This method mutates the passed graph.
502
+ */
503
+ function reorderGraph<T>(
504
+ graph: Graph<T>,
505
+ options: {shallow: boolean, ...},
506
+ ): void {
507
+ const orderedDependencies = new Map();
508
+
509
+ graph.entryPoints.forEach((entryPoint: string) => {
510
+ const mainModule = graph.dependencies.get(entryPoint);
511
+
512
+ if (!mainModule) {
513
+ throw new ReferenceError('Module not registered in graph: ' + entryPoint);
514
+ }
515
+
516
+ reorderDependencies(graph, mainModule, orderedDependencies, options);
517
+ });
518
+
519
+ graph.dependencies = orderedDependencies;
520
+ }
521
+
522
+ function reorderDependencies<T>(
523
+ graph: Graph<T>,
524
+ module: Module<T>,
525
+ orderedDependencies: Map<string, Module<T>>,
526
+ options: {shallow: boolean, ...},
527
+ ): void {
528
+ if (module.path) {
529
+ if (orderedDependencies.has(module.path)) {
530
+ return;
531
+ }
532
+
533
+ orderedDependencies.set(module.path, module);
534
+ }
535
+
536
+ module.dependencies.forEach((dependency: Dependency) => {
537
+ const path = dependency.absolutePath;
538
+ const childModule = graph.dependencies.get(path);
539
+
540
+ if (!childModule) {
541
+ if (dependency.data.data.asyncType != null || options.shallow) {
542
+ return;
543
+ } else {
544
+ throw new ReferenceError('Module not registered in graph: ' + path);
545
+ }
546
+ }
547
+
548
+ reorderDependencies(graph, childModule, orderedDependencies, options);
549
+ });
550
+ }
551
+
552
+ /** Garbage collection functions */
553
+
554
+ // Add an entry to importBundleNames (or increase the reference count of an existing one)
555
+ function incrementImportBundleReference<T>(
556
+ dependency: Dependency,
557
+ graph: Graph<T>,
558
+ ) {
559
+ const {absolutePath} = dependency;
560
+
561
+ graph.privateState.gc.importBundleRefs.set(
562
+ absolutePath,
563
+ (graph.privateState.gc.importBundleRefs.get(absolutePath) ?? 0) + 1,
564
+ );
565
+ graph.importBundleNames.add(absolutePath);
566
+ }
567
+
568
+ // Decrease the reference count of an entry in importBundleNames (and delete it if necessary)
569
+ function decrementImportBundleReference<T>(
570
+ dependency: Dependency,
571
+ graph: Graph<T>,
572
+ ) {
573
+ const {absolutePath} = dependency;
574
+
575
+ const prevRefCount = nullthrows(
576
+ graph.privateState.gc.importBundleRefs.get(absolutePath),
577
+ );
578
+ invariant(
579
+ prevRefCount > 0,
580
+ 'experimentalImportBundleSupport: import bundle refcount not valid',
581
+ );
582
+ graph.privateState.gc.importBundleRefs.set(absolutePath, prevRefCount - 1);
583
+ if (prevRefCount === 1) {
584
+ graph.privateState.gc.importBundleRefs.delete(absolutePath);
585
+ graph.importBundleNames.delete(absolutePath);
586
+ }
587
+ }
588
+
589
+ // Mark a module as in use (ref count >= 1)
590
+ function markModuleInUse<T>(module: Module<T>, graph: Graph<T>) {
591
+ graph.privateState.gc.color.set(module.path, 'black');
592
+ }
593
+
594
+ // Delete an unreachable module from the graph immediately, unless it's queued
595
+ // for later deletion as a potential cycle root. Delete the module's outbound
596
+ // edges.
597
+ // Called when the reference count of a module has reached 0.
598
+ function releaseModule<T>(
599
+ module: Module<T>,
600
+ graph: Graph<T>,
601
+ delta: Delta,
602
+ options: InternalOptions<T>,
603
+ ) {
604
+ for (const [relativePath, dependency] of module.dependencies) {
605
+ removeDependency(module, relativePath, dependency, graph, delta, options);
606
+ }
607
+ graph.privateState.gc.color.set(module.path, 'black');
608
+ if (!graph.privateState.gc.possibleCycleRoots.has(module.path)) {
609
+ freeModule(module, graph, delta);
610
+ }
611
+ }
612
+
613
+ // Delete an unreachable module from the graph.
614
+ function freeModule<T>(module: Module<T>, graph: Graph<T>, delta: Delta) {
615
+ if (delta.added.has(module.path)) {
616
+ // Mark the deletion by clearing a prior addition.
617
+ delta.added.delete(module.path);
618
+ } else {
619
+ // Mark the deletion in the deleted set.
620
+ delta.deleted.add(module.path);
621
+ delta.modified.delete(module.path);
622
+ }
623
+
624
+ // This module is not used anywhere else! We can clear it from the bundle.
625
+ // Clean up all the state associated with this module in order to correctly
626
+ // re-add it if we encounter it again.
627
+ graph.dependencies.delete(module.path);
628
+ delta.earlyInverseDependencies.delete(module.path);
629
+ graph.privateState.gc.possibleCycleRoots.delete(module.path);
630
+ graph.privateState.gc.color.delete(module.path);
631
+ }
632
+
633
+ // Mark a module as a possible cycle root
634
+ function markAsPossibleCycleRoot<T>(module: Module<T>, graph: Graph<T>) {
635
+ if (nullthrows(graph.privateState.gc.color.get(module.path)) !== 'purple') {
636
+ graph.privateState.gc.color.set(module.path, 'purple');
637
+ graph.privateState.gc.possibleCycleRoots.add(module.path);
638
+ }
639
+ }
640
+
641
+ // Collect any unreachable cycles in the graph.
642
+ function collectCycles<T>(graph: Graph<T>, delta: Delta) {
643
+ // Mark recursively from roots (trial deletion)
644
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
645
+ const module = nullthrows(graph.dependencies.get(path));
646
+ const color = nullthrows(graph.privateState.gc.color.get(path));
647
+ if (color === 'purple') {
648
+ markGray(module, graph);
649
+ } else {
650
+ graph.privateState.gc.possibleCycleRoots.delete(path);
651
+ if (
652
+ color === 'black' &&
653
+ module.inverseDependencies.size === 0 &&
654
+ !graph.entryPoints.has(path)
655
+ ) {
656
+ freeModule(module, graph, delta);
657
+ }
658
+ }
659
+ }
660
+ // Scan recursively from roots (undo unsuccessful trial deletions)
661
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
662
+ const module = nullthrows(graph.dependencies.get(path));
663
+ scan(module, graph);
664
+ }
665
+ // Collect recursively from roots (free unreachable cycles)
666
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
667
+ graph.privateState.gc.possibleCycleRoots.delete(path);
668
+ const module = nullthrows(graph.dependencies.get(path));
669
+ collectWhite(module, graph, delta);
670
+ }
671
+ }
672
+
673
+ function markGray<T>(module: Module<T>, graph: Graph<T>) {
674
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
675
+ if (color !== 'gray') {
676
+ graph.privateState.gc.color.set(module.path, 'gray');
677
+ for (const dependency of module.dependencies.values()) {
678
+ const childModule = nullthrows(
679
+ graph.dependencies.get(dependency.absolutePath),
680
+ );
681
+ // The inverse dependency will be restored during the scan phase if this module remains live.
682
+ childModule.inverseDependencies.delete(module.path);
683
+ markGray(childModule, graph);
684
+ }
685
+ }
686
+ }
687
+
688
+ function scan<T>(module: Module<T>, graph: Graph<T>) {
689
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
690
+ if (color === 'gray') {
691
+ if (
692
+ module.inverseDependencies.size > 0 ||
693
+ graph.entryPoints.has(module.path)
694
+ ) {
695
+ scanBlack(module, graph);
696
+ } else {
697
+ graph.privateState.gc.color.set(module.path, 'white');
698
+ for (const dependency of module.dependencies.values()) {
699
+ const childModule = nullthrows(
700
+ graph.dependencies.get(dependency.absolutePath),
701
+ );
702
+ scan(childModule, graph);
703
+ }
704
+ }
705
+ }
706
+ }
707
+
708
+ function scanBlack<T>(module: Module<T>, graph: Graph<T>) {
709
+ graph.privateState.gc.color.set(module.path, 'black');
710
+ for (const dependency of module.dependencies.values()) {
711
+ const childModule = nullthrows(
712
+ graph.dependencies.get(dependency.absolutePath),
713
+ );
714
+ // The inverse dependency must have been deleted during the mark phase.
715
+ childModule.inverseDependencies.add(module.path);
716
+ const childColor = nullthrows(
717
+ graph.privateState.gc.color.get(childModule.path),
718
+ );
719
+ if (childColor !== 'black') {
720
+ scanBlack(childModule, graph);
721
+ }
722
+ }
723
+ }
724
+
725
+ function collectWhite<T>(module: Module<T>, graph: Graph<T>, delta: Delta) {
726
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
727
+ if (
728
+ color === 'white' &&
729
+ !graph.privateState.gc.possibleCycleRoots.has(module.path)
730
+ ) {
731
+ graph.privateState.gc.color.set(module.path, 'black');
732
+ for (const dependency of module.dependencies.values()) {
733
+ const childModule = nullthrows(
734
+ graph.dependencies.get(dependency.absolutePath),
735
+ );
736
+ collectWhite(childModule, graph, delta);
737
+ }
738
+ freeModule(module, graph, delta);
739
+ }
740
+ }
741
+
742
+ /** End of garbage collection functions */
743
+
744
+ module.exports = {
745
+ createGraph,
746
+ initialTraverseDependencies,
747
+ traverseDependencies,
748
+ reorderGraph,
749
+ };