metro 0.71.0 → 0.71.3

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 (57) hide show
  1. package/package.json +23 -22
  2. package/src/Bundler.js +0 -2
  3. package/src/Bundler.js.flow +1 -1
  4. package/src/DeltaBundler/DeltaCalculator.js +2 -0
  5. package/src/DeltaBundler/DeltaCalculator.js.flow +2 -0
  6. package/src/DeltaBundler/Worker.flow.js +78 -0
  7. package/src/DeltaBundler/Worker.flow.js.flow +121 -0
  8. package/src/DeltaBundler/Worker.js +8 -66
  9. package/src/DeltaBundler/Worker.js.flow +8 -107
  10. package/src/DeltaBundler/WorkerFarm.js.flow +2 -2
  11. package/src/DeltaBundler/__fixtures__/hasteImpl.js +4 -0
  12. package/src/DeltaBundler/graphOperations.js +382 -222
  13. package/src/DeltaBundler/graphOperations.js.flow +404 -232
  14. package/src/DeltaBundler/types.flow.js +6 -0
  15. package/src/DeltaBundler/types.flow.js.flow +7 -1
  16. package/src/DeltaBundler.js +0 -2
  17. package/src/DeltaBundler.js.flow +1 -1
  18. package/src/HmrServer.js +0 -2
  19. package/src/HmrServer.js.flow +1 -2
  20. package/src/ModuleGraph/node-haste/node-haste.flow.js +0 -1
  21. package/src/ModuleGraph/node-haste/node-haste.flow.js.flow +1 -2
  22. package/src/ModuleGraph/node-haste/node-haste.js.flow +7 -2
  23. package/src/ModuleGraph/output/util.js.flow +2 -2
  24. package/src/ModuleGraph/silent-console.js +5 -4
  25. package/src/ModuleGraph/silent-console.js.flow +8 -4
  26. package/src/ModuleGraph/worker/collectDependencies.js +215 -8
  27. package/src/ModuleGraph/worker/collectDependencies.js.flow +233 -13
  28. package/src/Server.js +2 -0
  29. package/src/Server.js.flow +9 -2
  30. package/src/cli.js +5 -0
  31. package/src/cli.js.flow +5 -0
  32. package/src/commands/build.js +4 -3
  33. package/src/commands/build.js.flow +3 -1
  34. package/src/commands/serve.js +3 -3
  35. package/src/commands/serve.js.flow +3 -1
  36. package/src/index.flow.js +392 -0
  37. package/src/index.flow.js.flow +480 -0
  38. package/src/index.js +8 -380
  39. package/src/index.js.flow +8 -466
  40. package/src/lib/CountingSet.js +116 -0
  41. package/src/lib/CountingSet.js.flow +126 -0
  42. package/src/lib/JsonReporter.js +0 -2
  43. package/src/lib/JsonReporter.js.flow +1 -1
  44. package/src/lib/getAppendScripts.js +10 -4
  45. package/src/lib/getAppendScripts.js.flow +6 -4
  46. package/src/lib/getPreludeCode.js +19 -1
  47. package/src/lib/getPreludeCode.js.flow +15 -0
  48. package/src/lib/getPrependedScripts.js +10 -2
  49. package/src/lib/getPrependedScripts.js.flow +11 -2
  50. package/src/lib/reporting.js +0 -2
  51. package/src/lib/reporting.js.flow +2 -1
  52. package/src/node-haste/DependencyGraph/ModuleResolution.js +15 -3
  53. package/src/node-haste/DependencyGraph/ModuleResolution.js.flow +15 -0
  54. package/src/node-haste/DependencyGraph/createHasteMap.js +78 -19
  55. package/src/node-haste/DependencyGraph/createHasteMap.js.flow +14 -9
  56. package/src/node-haste/DependencyGraph.js +2 -2
  57. package/src/node-haste/DependencyGraph.js.flow +4 -2
@@ -8,6 +8,26 @@
8
8
  * @format
9
9
  */
10
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
+
11
31
  'use strict';
12
32
 
13
33
  import type {
@@ -19,11 +39,38 @@ import type {
19
39
  TransformResultDependency,
20
40
  } from './types.flow';
21
41
 
42
+ import CountingSet from '../lib/CountingSet';
43
+
44
+ const invariant = require('invariant');
22
45
  const nullthrows = require('nullthrows');
23
46
 
47
+ // TODO: Convert to a Flow enum
48
+ type NodeColor =
49
+ // In use or free
50
+ | 'black'
51
+
52
+ // Possible member of cycle
53
+ | 'gray'
54
+
55
+ // Member of garbage cycle
56
+ | 'white'
57
+
58
+ // Possible root of cycle
59
+ | 'purple'
60
+
61
+ // Inherently acyclic node (Not currently used)
62
+ | 'green';
63
+
24
64
  // Private state for the graph that persists between operations.
25
65
  export opaque type PrivateState = {
26
- // TODO: Track e.g. GC scheduling here.
66
+ +gc: {
67
+ // GC state for nodes in the graph (graph.dependencies)
68
+ +color: Map<string, NodeColor>,
69
+ +possibleCycleRoots: Set<string>,
70
+
71
+ // Reference counts for entries in importBundleNames
72
+ +importBundleRefs: Map<string, number>,
73
+ },
27
74
  };
28
75
 
29
76
  function createGraph<T>(options: GraphInputOptions): Graph<T> {
@@ -31,7 +78,13 @@ function createGraph<T>(options: GraphInputOptions): Graph<T> {
31
78
  ...options,
32
79
  dependencies: new Map(),
33
80
  importBundleNames: new Set(),
34
- privateState: {},
81
+ privateState: {
82
+ gc: {
83
+ color: new Map(),
84
+ possibleCycleRoots: new Set(),
85
+ importBundleRefs: new Map(),
86
+ },
87
+ },
35
88
  };
36
89
  }
37
90
 
@@ -39,7 +92,6 @@ type Result<T> = {
39
92
  added: Map<string, Module<T>>,
40
93
  modified: Map<string, Module<T>>,
41
94
  deleted: Set<string>,
42
- ...
43
95
  };
44
96
 
45
97
  /**
@@ -51,7 +103,10 @@ type Delta = $ReadOnly<{
51
103
  added: Set<string>,
52
104
  modified: Set<string>,
53
105
  deleted: Set<string>,
54
- inverseDependencies: Map<string, Set<string>>,
106
+
107
+ // A place to temporarily track inverse dependencies for a module while it is
108
+ // being processed but has not been added to `graph.dependencies` yet.
109
+ earlyInverseDependencies: Map<string, CountingSet<string>>,
55
110
  }>;
56
111
 
57
112
  type InternalOptions<T> = $ReadOnly<{
@@ -103,7 +158,7 @@ async function traverseDependencies<T>(
103
158
  added: new Set(),
104
159
  modified: new Set(),
105
160
  deleted: new Set(),
106
- inverseDependencies: new Map(),
161
+ earlyInverseDependencies: new Map(),
107
162
  };
108
163
 
109
164
  const internalOptions = getInternalOptions(options);
@@ -122,6 +177,8 @@ async function traverseDependencies<T>(
122
177
  }
123
178
  }
124
179
 
180
+ collectCycles(graph, delta);
181
+
125
182
  const added = new Map();
126
183
  for (const path of delta.added) {
127
184
  added.set(path, nullthrows(graph.dependencies.get(path)));
@@ -150,11 +207,29 @@ async function initialTraverseDependencies<T>(
150
207
  added: new Set(),
151
208
  modified: new Set(),
152
209
  deleted: new Set(),
153
- inverseDependencies: new Map(),
210
+ earlyInverseDependencies: new Map(),
154
211
  };
155
212
 
156
213
  const internalOptions = getInternalOptions(options);
157
214
 
215
+ invariant(
216
+ graph.dependencies.size === 0,
217
+ 'initialTraverseDependencies called on nonempty graph',
218
+ );
219
+ invariant(
220
+ graph.importBundleNames.size === 0,
221
+ 'initialTraverseDependencies called on nonempty graph',
222
+ );
223
+
224
+ graph.privateState.gc.color.clear();
225
+ graph.privateState.gc.possibleCycleRoots.clear();
226
+ graph.privateState.gc.importBundleRefs.clear();
227
+
228
+ for (const path of graph.entryPoints) {
229
+ // Each entry point implicitly has a refcount of 1, so mark them all black.
230
+ graph.privateState.gc.color.set(path, 'black');
231
+ }
232
+
158
233
  await Promise.all(
159
234
  [...graph.entryPoints].map((path: string) =>
160
235
  traverseDependenciesForSingleFile(path, graph, delta, internalOptions),
@@ -204,7 +279,8 @@ async function processModule<T>(
204
279
  );
205
280
 
206
281
  const previousModule = graph.dependencies.get(path) || {
207
- inverseDependencies: delta.inverseDependencies.get(path) || new Set(),
282
+ inverseDependencies:
283
+ delta.earlyInverseDependencies.get(path) || new CountingSet(),
208
284
  path,
209
285
  };
210
286
  const previousDependencies = previousModule.dependencies || new Map();
@@ -212,273 +288,177 @@ async function processModule<T>(
212
288
  // Update the module information.
213
289
  const module = {
214
290
  ...previousModule,
215
- dependencies: new Map(),
291
+ dependencies: new Map(previousDependencies),
216
292
  getSource: result.getSource,
217
293
  output: result.output,
218
294
  };
219
295
  graph.dependencies.set(module.path, module);
220
296
 
221
- for (const [relativePath, dependency] of currentDependencies) {
222
- module.dependencies.set(relativePath, dependency);
223
- }
224
-
225
- Array.from(previousDependencies.entries())
226
- .filter(
227
- ([relativePath, dependency]) =>
228
- !currentDependencies.has(relativePath) ||
229
- nullthrows(currentDependencies.get(relativePath)).absolutePath !==
230
- dependency.absolutePath,
231
- )
232
- .forEach(([relativePath, dependency]) =>
297
+ // Diff dependencies (1/2): remove dependencies that have changed or been removed.
298
+ for (const [relativePath, prevDependency] of previousDependencies) {
299
+ const curDependency = currentDependencies.get(relativePath);
300
+ if (
301
+ !curDependency ||
302
+ curDependency.absolutePath !== prevDependency.absolutePath ||
303
+ (options.experimentalImportBundleSupport &&
304
+ curDependency.data.data.asyncType !==
305
+ prevDependency.data.data.asyncType)
306
+ ) {
233
307
  removeDependency(
234
308
  module,
235
- dependency.absolutePath,
309
+ relativePath,
310
+ prevDependency,
236
311
  graph,
237
312
  delta,
238
- new Set(),
239
- ),
240
- );
313
+ options,
314
+ );
315
+ }
316
+ }
241
317
 
242
- // Check all the module dependencies and start traversing the tree from each
243
- // added and removed dependency, to get all the modules that have to be added
244
- // and removed from the dependency graph.
318
+ // Diff dependencies (2/2): add dependencies that have changed or been added.
245
319
  const promises = [];
246
-
247
- for (const [relativePath, dependency] of currentDependencies) {
248
- if (!options.shallow) {
249
- if (
250
- options.experimentalImportBundleSupport &&
251
- dependency.data.data.asyncType != null
252
- ) {
253
- graph.importBundleNames.add(dependency.absolutePath);
254
- } else if (
255
- !previousDependencies.has(relativePath) ||
256
- nullthrows(previousDependencies.get(relativePath)).absolutePath !==
257
- dependency.absolutePath
258
- ) {
259
- promises.push(
260
- addDependency(module, dependency.absolutePath, graph, delta, options),
261
- );
262
- }
320
+ for (const [relativePath, curDependency] of currentDependencies) {
321
+ const prevDependency = previousDependencies.get(relativePath);
322
+ if (
323
+ !prevDependency ||
324
+ prevDependency.absolutePath !== curDependency.absolutePath ||
325
+ (options.experimentalImportBundleSupport &&
326
+ prevDependency.data.data.asyncType !==
327
+ curDependency.data.data.asyncType)
328
+ ) {
329
+ promises.push(
330
+ addDependency(
331
+ module,
332
+ relativePath,
333
+ curDependency,
334
+ graph,
335
+ delta,
336
+ options,
337
+ ),
338
+ );
263
339
  }
264
340
  }
265
341
 
266
- try {
267
- await Promise.all(promises);
268
- } catch (err) {
269
- // If there is an error, restore the previous dependency list.
270
- // This ensures we don't skip over them during the next traversal attempt.
271
- // $FlowFixMe[cannot-write]
272
- module.dependencies = previousDependencies;
273
- throw err;
274
- }
342
+ await Promise.all(promises);
343
+
344
+ // Replace dependencies with the correctly-ordered version. As long as all
345
+ // the above promises have resolved, this will be the same map but without
346
+ // the added nondeterminism of promise resolution order. Because this
347
+ // assignment does not add or remove edges, it does NOT invalidate any of the
348
+ // garbage collection state.
349
+
350
+ // Catch obvious errors with a cheap assertion.
351
+ invariant(
352
+ module.dependencies.size === currentDependencies.size,
353
+ 'Failed to add the correct dependencies',
354
+ );
355
+
356
+ // $FlowFixMe[cannot-write]
357
+ module.dependencies = currentDependencies;
358
+
275
359
  return module;
276
360
  }
277
361
 
278
362
  async function addDependency<T>(
279
363
  parentModule: Module<T>,
280
- path: string,
364
+ relativePath: string,
365
+ dependency: Dependency,
281
366
  graph: Graph<T>,
282
367
  delta: Delta,
283
368
  options: InternalOptions<T>,
284
369
  ): Promise<void> {
285
- // The new dependency was already in the graph, we don't need to do anything.
286
- const existingModule = graph.dependencies.get(path);
287
-
288
- if (existingModule) {
289
- existingModule.inverseDependencies.add(parentModule.path);
290
-
291
- return;
292
- }
293
-
294
- // This module is being transformed at the moment in parallel, so we should
295
- // only mark its parent as an inverse dependency.
296
- const inverse = delta.inverseDependencies.get(path);
297
- if (inverse) {
298
- inverse.add(parentModule.path);
299
-
300
- return;
301
- }
302
-
303
- if (delta.deleted.has(path)) {
304
- // Mark the addition by clearing a prior deletion.
305
- delta.deleted.delete(path);
370
+ const path = dependency.absolutePath;
371
+
372
+ // The module may already exist, in which case we just need to update some
373
+ // bookkeeping instead of adding a new node to the graph.
374
+ let module = graph.dependencies.get(path);
375
+
376
+ if (options.shallow) {
377
+ // Don't add a node for the module if the graph is shallow (single-module).
378
+ } else if (
379
+ options.experimentalImportBundleSupport &&
380
+ dependency.data.data.asyncType != null
381
+ ) {
382
+ // Don't add a node for the module if we are traversing async dependencies
383
+ // lazily (and this is an async dependency). Instead, record it in
384
+ // importBundleNames.
385
+ incrementImportBundleReference(dependency, graph);
306
386
  } else {
307
- // Mark the addition in the added set.
308
- delta.added.add(path);
309
- delta.modified.delete(path);
310
- }
311
- delta.inverseDependencies.set(path, new Set([parentModule.path]));
312
-
313
- options.onDependencyAdd();
314
-
315
- const module = await processModule(path, graph, delta, options);
316
-
317
- graph.dependencies.set(module.path, module);
318
- module.inverseDependencies.add(parentModule.path);
319
-
320
- options.onDependencyAdded();
321
- }
322
-
323
- /**
324
- * Recursively look up `inverseDependencies` until it is empty,
325
- * returning a set of paths for the last module that does not have
326
- * `inverseDependencies`.
327
- */
328
- function getAllTopLevelInverseDependencies<T>(
329
- inverseDependencies: Set<string>,
330
- graph: Graph<T>,
331
- currModule: string,
332
- visited: Set<string>,
333
- ): Set<string> {
334
- if (visited.has(currModule)) {
335
- return new Set();
336
- }
337
- visited.add(currModule);
338
- if (!inverseDependencies.size) {
339
- return new Set([currModule]);
340
- }
341
-
342
- return Array.from(inverseDependencies)
343
- .filter(inverseDep => graph.dependencies.has(inverseDep))
344
- .reduce((acc, inverseDep) => {
345
- const mod = graph.dependencies.get(inverseDep);
346
- if (!mod) {
347
- return acc;
387
+ if (!module) {
388
+ // Add a new node to the graph.
389
+ const earlyInverseDependencies = delta.earlyInverseDependencies.get(path);
390
+ if (earlyInverseDependencies) {
391
+ // This module is being transformed at the moment in parallel, so we
392
+ // should only mark its parent as an inverse dependency.
393
+ earlyInverseDependencies.add(parentModule.path);
394
+ } else {
395
+ if (delta.deleted.has(path)) {
396
+ // Mark the addition by clearing a prior deletion.
397
+ delta.deleted.delete(path);
398
+ } else {
399
+ // Mark the addition in the added set.
400
+ delta.added.add(path);
401
+ delta.modified.delete(path);
402
+ }
403
+ delta.earlyInverseDependencies.set(path, new CountingSet());
404
+
405
+ options.onDependencyAdd();
406
+ module = await processModule(path, graph, delta, options);
407
+ options.onDependencyAdded();
408
+
409
+ graph.dependencies.set(module.path, module);
348
410
  }
349
- getAllTopLevelInverseDependencies(
350
- mod.inverseDependencies,
351
- graph,
352
- inverseDep,
353
- visited,
354
- ).forEach(x => {
355
- acc.add(x);
356
- });
357
- return acc;
358
- }, new Set());
359
- }
360
-
361
- /**
362
- * Given `inverseDependencies`, tracing back inverse dependencies to
363
- * see if it only leads back to `parentModule`.
364
- */
365
- function canSafelyRemoveFromParentModule<T>(
366
- inverseDependencies: Set<string>,
367
- parentModule: string,
368
- graph: Graph<T>,
369
- canBeRemovedSafely: Set<string>,
370
- delta: Delta,
371
- ): boolean {
372
- const visited = new Set();
373
- const topInverseDependencies = getAllTopLevelInverseDependencies(
374
- inverseDependencies,
375
- graph,
376
- '', // current module name
377
- visited,
378
- );
379
-
380
- if (!topInverseDependencies.size) {
381
- /**
382
- * This happens when parentModule and inverseDependencies have a circular dependency.
383
- * This will eventually become an empty set due to the `visited` Set being the
384
- * base case for the recursive call.
385
- */
386
- return true;
411
+ }
412
+ if (module) {
413
+ // We either added a new node to the graph, or we're updating an existing one.
414
+ module.inverseDependencies.add(parentModule.path);
415
+ markModuleInUse(module, graph);
416
+ }
387
417
  }
388
418
 
389
- const undeletedInverseDependencies = Array.from(
390
- topInverseDependencies,
391
- ).filter(x => graph.dependencies.has(x));
392
-
393
- /**
394
- * We can only mark the `visited` Set of modules to be safely removable if
395
- * 1. We do not have top a level module to compare with parentModule.
396
- * This can happen when trying to see if we can safely remove from
397
- * a module that was deleted. This is why we filtered them out with `delta.deleted`
398
- * 2. We have one top module and it is parentModule
399
- */
400
- const canSafelyRemove =
401
- !undeletedInverseDependencies.length ||
402
- (undeletedInverseDependencies.length === 1 &&
403
- undeletedInverseDependencies[0] === parentModule);
404
-
405
- if (canSafelyRemove) {
406
- visited.forEach(mod => {
407
- canBeRemovedSafely.add(mod);
408
- });
409
- }
410
- return canSafelyRemove;
419
+ // Always update the parent's dependency map.
420
+ // This means the parent's dependencies can get desynced from
421
+ // inverseDependencies and the other fields in the case of lazy edges.
422
+ // Not an optimal representation :(
423
+ parentModule.dependencies.set(relativePath, dependency);
411
424
  }
412
425
 
413
426
  function removeDependency<T>(
414
427
  parentModule: Module<T>,
415
- absolutePath: string,
428
+ relativePath: string,
429
+ dependency: Dependency,
416
430
  graph: Graph<T>,
417
431
  delta: Delta,
418
- // We use `canBeRemovedSafely` set to keep track of visited
419
- // module(s) that we're sure can be removed. This will skip expensive
420
- // inverse dependency traversals.
421
- canBeRemovedSafely: Set<string> = new Set(),
432
+ options: InternalOptions<T>,
422
433
  ): void {
434
+ parentModule.dependencies.delete(relativePath);
435
+
436
+ const {absolutePath} = dependency;
437
+
438
+ if (
439
+ options.experimentalImportBundleSupport &&
440
+ dependency.data.data.asyncType != null
441
+ ) {
442
+ decrementImportBundleReference(dependency, graph);
443
+ }
444
+
423
445
  const module = graph.dependencies.get(absolutePath);
424
446
 
425
447
  if (!module) {
426
448
  return;
427
449
  }
428
-
429
450
  module.inverseDependencies.delete(parentModule.path);
430
-
431
- // Even if there are modules still using parentModule, we want to ensure
432
- // there is no circular dependency. Thus, we check if it can be safely removed
433
- // by tracing back the inverseDependencies.
434
- if (!canBeRemovedSafely.has(module.path)) {
435
- if (
436
- module.inverseDependencies.size &&
437
- !canSafelyRemoveFromParentModule(
438
- module.inverseDependencies,
439
- module.path,
440
- graph,
441
- canBeRemovedSafely,
442
- delta,
443
- )
444
- ) {
445
- return;
446
- }
447
- }
448
-
449
- if (delta.added.has(module.path)) {
450
- // Mark the deletion by clearing a prior addition.
451
- delta.added.delete(module.path);
451
+ if (
452
+ module.inverseDependencies.size > 0 ||
453
+ graph.entryPoints.has(absolutePath)
454
+ ) {
455
+ // The reference count has decreased, but not to zero.
456
+ // NOTE: Each entry point implicitly has a refcount of 1.
457
+ markAsPossibleCycleRoot(module, graph);
452
458
  } else {
453
- // Mark the deletion in the deleted set.
454
- delta.deleted.add(module.path);
455
- delta.modified.delete(module.path);
459
+ // The reference count has decreased to zero.
460
+ releaseModule(module, graph, delta, options);
456
461
  }
457
-
458
- // This module is not used anywhere else! We can clear it from the bundle.
459
- // Clean up all the state associated with this module in order to correctly
460
- // re-add it if we encounter it again.
461
- graph.dependencies.delete(module.path);
462
- delta.inverseDependencies.delete(module.path);
463
-
464
- // Now we need to iterate through the module dependencies in order to
465
- // clean up everything (we cannot read the module because it may have
466
- // been deleted).
467
- Array.from(module.dependencies.values())
468
- .filter(
469
- dependency =>
470
- graph.dependencies.has(dependency.absolutePath) &&
471
- dependency.absolutePath !== parentModule.path,
472
- )
473
- .forEach(dependency =>
474
- removeDependency(
475
- module,
476
- dependency.absolutePath,
477
- graph,
478
- delta,
479
- canBeRemovedSafely,
480
- ),
481
- );
482
462
  }
483
463
 
484
464
  function resolveDependencies<T>(
@@ -572,6 +552,198 @@ function reorderDependencies<T>(
572
552
  });
573
553
  }
574
554
 
555
+ /** Garbage collection functions */
556
+
557
+ // Add an entry to importBundleNames (or increase the reference count of an existing one)
558
+ function incrementImportBundleReference<T>(
559
+ dependency: Dependency,
560
+ graph: Graph<T>,
561
+ ) {
562
+ const {absolutePath} = dependency;
563
+
564
+ graph.privateState.gc.importBundleRefs.set(
565
+ absolutePath,
566
+ (graph.privateState.gc.importBundleRefs.get(absolutePath) ?? 0) + 1,
567
+ );
568
+ graph.importBundleNames.add(absolutePath);
569
+ }
570
+
571
+ // Decrease the reference count of an entry in importBundleNames (and delete it if necessary)
572
+ function decrementImportBundleReference<T>(
573
+ dependency: Dependency,
574
+ graph: Graph<T>,
575
+ ) {
576
+ const {absolutePath} = dependency;
577
+
578
+ const prevRefCount = nullthrows(
579
+ graph.privateState.gc.importBundleRefs.get(absolutePath),
580
+ );
581
+ invariant(
582
+ prevRefCount > 0,
583
+ 'experimentalImportBundleSupport: import bundle refcount not valid',
584
+ );
585
+ graph.privateState.gc.importBundleRefs.set(absolutePath, prevRefCount - 1);
586
+ if (prevRefCount === 1) {
587
+ graph.privateState.gc.importBundleRefs.delete(absolutePath);
588
+ graph.importBundleNames.delete(absolutePath);
589
+ }
590
+ }
591
+
592
+ // Mark a module as in use (ref count >= 1)
593
+ function markModuleInUse<T>(module: Module<T>, graph: Graph<T>) {
594
+ graph.privateState.gc.color.set(module.path, 'black');
595
+ }
596
+
597
+ // Delete an unreachable module from the graph immediately, unless it's queued
598
+ // for later deletion as a potential cycle root. Delete the module's outbound
599
+ // edges.
600
+ // Called when the reference count of a module has reached 0.
601
+ function releaseModule<T>(
602
+ module: Module<T>,
603
+ graph: Graph<T>,
604
+ delta: Delta,
605
+ options: InternalOptions<T>,
606
+ ) {
607
+ for (const [relativePath, dependency] of module.dependencies) {
608
+ removeDependency(module, relativePath, dependency, graph, delta, options);
609
+ }
610
+ graph.privateState.gc.color.set(module.path, 'black');
611
+ if (!graph.privateState.gc.possibleCycleRoots.has(module.path)) {
612
+ freeModule(module, graph, delta);
613
+ }
614
+ }
615
+
616
+ // Delete an unreachable module from the graph.
617
+ function freeModule<T>(module: Module<T>, graph: Graph<T>, delta: Delta) {
618
+ if (delta.added.has(module.path)) {
619
+ // Mark the deletion by clearing a prior addition.
620
+ delta.added.delete(module.path);
621
+ } else {
622
+ // Mark the deletion in the deleted set.
623
+ delta.deleted.add(module.path);
624
+ delta.modified.delete(module.path);
625
+ }
626
+
627
+ // This module is not used anywhere else! We can clear it from the bundle.
628
+ // Clean up all the state associated with this module in order to correctly
629
+ // re-add it if we encounter it again.
630
+ graph.dependencies.delete(module.path);
631
+ delta.earlyInverseDependencies.delete(module.path);
632
+ graph.privateState.gc.possibleCycleRoots.delete(module.path);
633
+ graph.privateState.gc.color.delete(module.path);
634
+ }
635
+
636
+ // Mark a module as a possible cycle root
637
+ function markAsPossibleCycleRoot<T>(module: Module<T>, graph: Graph<T>) {
638
+ if (nullthrows(graph.privateState.gc.color.get(module.path)) !== 'purple') {
639
+ graph.privateState.gc.color.set(module.path, 'purple');
640
+ graph.privateState.gc.possibleCycleRoots.add(module.path);
641
+ }
642
+ }
643
+
644
+ // Collect any unreachable cycles in the graph.
645
+ function collectCycles<T>(graph: Graph<T>, delta: Delta) {
646
+ // Mark recursively from roots (trial deletion)
647
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
648
+ const module = nullthrows(graph.dependencies.get(path));
649
+ const color = nullthrows(graph.privateState.gc.color.get(path));
650
+ if (color === 'purple') {
651
+ markGray(module, graph);
652
+ } else {
653
+ graph.privateState.gc.possibleCycleRoots.delete(path);
654
+ if (
655
+ color === 'black' &&
656
+ module.inverseDependencies.size === 0 &&
657
+ !graph.entryPoints.has(path)
658
+ ) {
659
+ freeModule(module, graph, delta);
660
+ }
661
+ }
662
+ }
663
+ // Scan recursively from roots (undo unsuccessful trial deletions)
664
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
665
+ const module = nullthrows(graph.dependencies.get(path));
666
+ scan(module, graph);
667
+ }
668
+ // Collect recursively from roots (free unreachable cycles)
669
+ for (const path of graph.privateState.gc.possibleCycleRoots) {
670
+ graph.privateState.gc.possibleCycleRoots.delete(path);
671
+ const module = nullthrows(graph.dependencies.get(path));
672
+ collectWhite(module, graph, delta);
673
+ }
674
+ }
675
+
676
+ function markGray<T>(module: Module<T>, graph: Graph<T>) {
677
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
678
+ if (color !== 'gray') {
679
+ graph.privateState.gc.color.set(module.path, 'gray');
680
+ for (const dependency of module.dependencies.values()) {
681
+ const childModule = nullthrows(
682
+ graph.dependencies.get(dependency.absolutePath),
683
+ );
684
+ // The inverse dependency will be restored during the scan phase if this module remains live.
685
+ childModule.inverseDependencies.delete(module.path);
686
+ markGray(childModule, graph);
687
+ }
688
+ }
689
+ }
690
+
691
+ function scan<T>(module: Module<T>, graph: Graph<T>) {
692
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
693
+ if (color === 'gray') {
694
+ if (
695
+ module.inverseDependencies.size > 0 ||
696
+ graph.entryPoints.has(module.path)
697
+ ) {
698
+ scanBlack(module, graph);
699
+ } else {
700
+ graph.privateState.gc.color.set(module.path, 'white');
701
+ for (const dependency of module.dependencies.values()) {
702
+ const childModule = nullthrows(
703
+ graph.dependencies.get(dependency.absolutePath),
704
+ );
705
+ scan(childModule, graph);
706
+ }
707
+ }
708
+ }
709
+ }
710
+
711
+ function scanBlack<T>(module: Module<T>, graph: Graph<T>) {
712
+ graph.privateState.gc.color.set(module.path, 'black');
713
+ for (const dependency of module.dependencies.values()) {
714
+ const childModule = nullthrows(
715
+ graph.dependencies.get(dependency.absolutePath),
716
+ );
717
+ // The inverse dependency must have been deleted during the mark phase.
718
+ childModule.inverseDependencies.add(module.path);
719
+ const childColor = nullthrows(
720
+ graph.privateState.gc.color.get(childModule.path),
721
+ );
722
+ if (childColor !== 'black') {
723
+ scanBlack(childModule, graph);
724
+ }
725
+ }
726
+ }
727
+
728
+ function collectWhite<T>(module: Module<T>, graph: Graph<T>, delta: Delta) {
729
+ const color = nullthrows(graph.privateState.gc.color.get(module.path));
730
+ if (
731
+ color === 'white' &&
732
+ !graph.privateState.gc.possibleCycleRoots.has(module.path)
733
+ ) {
734
+ graph.privateState.gc.color.set(module.path, 'black');
735
+ for (const dependency of module.dependencies.values()) {
736
+ const childModule = nullthrows(
737
+ graph.dependencies.get(dependency.absolutePath),
738
+ );
739
+ collectWhite(childModule, graph, delta);
740
+ }
741
+ freeModule(module, graph, delta);
742
+ }
743
+ }
744
+
745
+ /** End of garbage collection functions */
746
+
575
747
  module.exports = {
576
748
  createGraph,
577
749
  initialTraverseDependencies,