PHP WebShell

Текущая директория: /usr/lib/node_modules/bitgo/node_modules/metro/src/DeltaBundler

Просмотр файла: Graph.js.flow

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 * @oncall react_native
 */

/**
 * Portions of this code are based on the Synchronous Cycle Collection
 * algorithm described in:
 *
 * David F. Bacon and V. T. Rajan. 2001. Concurrent Cycle Collection in
 * Reference Counted Systems. In Proceedings of the 15th European Conference on
 * Object-Oriented Programming (ECOOP '01). Springer-Verlag, Berlin,
 * Heidelberg, 207–235.
 *
 * Notable differences from the algorithm in the paper:
 * 1. Our implementation uses the inverseDependencies set (which we already
 *    have to maintain) instead of a separate refcount variable. A module's
 *    reference count is equal to the size of its inverseDependencies set, plus
 *    1 if it's an entry point of the graph.
 * 2. We keep the "root buffer" (possibleCycleRoots) free of duplicates by
 *    making it a Set, instead of storing a "buffered" flag on each node.
 * 3. On top of tracking edges between nodes, we also count references between
 *    nodes and entries in the importBundleNodes set.
 */

import type {RequireContext} from '../lib/contextModule';
import type {RequireContextParams} from '../ModuleGraph/worker/collectDependencies';
import type {
  Dependencies,
  Dependency,
  GraphInputOptions,
  MixedOutput,
  Module,
  ModuleData,
  Options,
  ResolvedDependency,
  TransformInputOptions,
} from './types';

import {fileMatchesContext} from '../lib/contextModule';
import CountingSet from '../lib/CountingSet';
import {isResolvedDependency} from '../lib/isResolvedDependency';
import {buildSubgraph} from './buildSubgraph';
import invariant from 'invariant';
import nullthrows from 'nullthrows';

// TODO: Convert to a Flow enum
type NodeColor =
  // In use or free
  | 'black'

  // Possible member of cycle
  | 'gray'

  // Member of garbage cycle
  | 'white'

  // Possible root of cycle
  | 'purple'

  // Inherently acyclic node (Not currently used)
  | 'green';

export type Result<T> = {
  added: Map<string, Module<T>>,
  modified: Map<string, Module<T>>,
  deleted: Set<string>,
};

/**
 * Internal data structure that the traversal logic uses to know which of the
 * files have been modified. This allows to return the added modules before the
 * modified ones (which is useful for things like Hot Module Reloading).
 **/
type Delta<T> = $ReadOnly<{
  // `added` and `deleted` are mutually exclusive.
  // Internally, a module can be in both `touched` and (either) `added` or
  // `deleted`. Before returning the result, we'll calculate
  // modified = touched - added - deleted.
  added: Set<string>,
  touched: Set<string>,
  deleted: Set<string>,

  updatedModuleData: $ReadOnlyMap<string, ModuleData<T>>,
  baseModuleData: Map<string, ModuleData<T>>,
  errors: $ReadOnlyMap<string, Error>,
}>;

type InternalOptions<T> = $ReadOnly<{
  lazy: boolean,
  onDependencyAdd: () => mixed,
  onDependencyAdded: () => mixed,
  resolve: Options<T>['resolve'],
  transform: Options<T>['transform'],
  shallow: boolean,
}>;

function getInternalOptions<T>({
  transform,
  resolve,
  onProgress,
  lazy,
  shallow,
}: Options<T>): InternalOptions<T> {
  let numProcessed = 0;
  let total = 0;

  return {
    lazy,
    transform,
    resolve,
    onDependencyAdd: () => onProgress && onProgress(numProcessed, ++total),
    onDependencyAdded: () => onProgress && onProgress(++numProcessed, total),
    shallow,
  };
}

function isWeakOrLazy<T>(
  dependency: ResolvedDependency,
  options: InternalOptions<T>,
): boolean {
  const asyncType = dependency.data.data.asyncType;
  return asyncType === 'weak' || (asyncType != null && options.lazy);
}

export class Graph<T = MixedOutput> {
  +entryPoints: $ReadOnlySet<string>;
  +transformOptions: TransformInputOptions;
  +dependencies: Dependencies<T> = new Map();
  +#importBundleNodes: Map<
    string,
    $ReadOnly<{
      inverseDependencies: CountingSet<string>,
    }>,
  > = new Map();

  /// GC state for nodes in the graph (this.dependencies)
  +#gc: {
    +color: Map<string, NodeColor>,
    +possibleCycleRoots: Set<string>,
  } = {
    color: new Map(),
    possibleCycleRoots: new Set(),
  };

  /** Resolved context parameters from `require.context`. */
  #resolvedContexts: Map<string, RequireContext> = new Map();

  constructor(options: GraphInputOptions) {
    this.entryPoints = options.entryPoints;
    this.transformOptions = options.transformOptions;
  }

  /**
   * Dependency Traversal logic for the Delta Bundler. This method calculates
   * the modules that should be included in the bundle by traversing the
   * dependency graph.
   * Instead of traversing the whole graph each time, it just calculates the
   * difference between runs by only traversing the added/removed dependencies.
   * To do so, it uses the passed graph dependencies and it mutates it.
   * The paths parameter contains the absolute paths of the root files that the
   * method should traverse. Normally, these paths should be the modified files
   * since the last traversal.
   */
  async traverseDependencies(
    paths: $ReadOnlyArray<string>,
    options: Options<T>,
  ): Promise<Result<T>> {
    const internalOptions = getInternalOptions(options);

    const modifiedPathsInBaseGraph = new Set(
      paths.filter(path => this.dependencies.has(path)),
    );

    const allModifiedPaths = new Set(paths);

    const delta = await this._buildDelta(
      modifiedPathsInBaseGraph,
      internalOptions,
      // Traverse new or modified paths
      absolutePath =>
        !this.dependencies.has(absolutePath) ||
        allModifiedPaths.has(absolutePath),
    );

    // If we have errors we might need to roll back any changes - take
    // snapshots of all modified modules at the base state. We'll also snapshot
    // unmodified modules that become unreachable as they are released, so that
    // we have everything we need to restore the graph to base.
    if (delta.errors.size > 0) {
      for (const modified of modifiedPathsInBaseGraph) {
        delta.baseModuleData.set(
          modified,
          this._moduleSnapshot(nullthrows(this.dependencies.get(modified))),
        );
      }
    }

    // Commit changes in a subtractive pass and then an additive pass - this
    // ensures that any errors encountered on the additive pass would also be
    // encountered on a fresh build (implying legitimate errors in the graph,
    // rather than an error in a module that's no longer reachable).
    for (const modified of modifiedPathsInBaseGraph) {
      // Skip this module if it has errors. Hopefully it will be removed -
      // if not, we'll throw during the additive pass.
      if (delta.errors.has(modified)) {
        continue;
      }
      const module = this.dependencies.get(modified);
      // The module may have already been released from the graph - we'll readd
      // it if necessary later.
      if (module == null) {
        continue;
      }
      // Process the transform result and dependency removals. This should
      // never encounter an error.
      this._recursivelyCommitModule(modified, delta, internalOptions, {
        onlyRemove: true,
      });
    }

    // Ensure we have released any unreachable modules before the additive
    // pass.
    this._collectCycles(delta, internalOptions);

    // Additive pass - any errors we encounter here should be thrown after
    // rolling back the commit.
    try {
      for (const modified of modifiedPathsInBaseGraph) {
        const module = this.dependencies.get(modified);
        // The module may have already been released from the graph (it may yet
        // be readded via another dependency).
        if (module == null) {
          continue;
        }

        this._recursivelyCommitModule(modified, delta, internalOptions);
      }
    } catch (error) {
      // Roll back to base before re-throwing.
      const rollbackDelta: Delta<T> = {
        added: delta.added,
        deleted: delta.deleted,
        touched: new Set(),
        updatedModuleData: delta.baseModuleData,
        baseModuleData: new Map(),
        errors: new Map(),
      };
      for (const modified of modifiedPathsInBaseGraph) {
        const module = this.dependencies.get(modified);
        // The module may have already been released from the graph (it may yet
        // be readded via another dependency).
        if (module == null) {
          continue;
        }
        // Set the module and descendants back to base state.
        this._recursivelyCommitModule(modified, rollbackDelta, internalOptions);
      }
      // Collect cycles again after rolling back. There's no need if we're
      // not rolling back, because we have not removed any edges.
      this._collectCycles(delta, internalOptions);

      // Cheap check to validate the rollback.
      invariant(
        rollbackDelta.added.size === 0 && rollbackDelta.deleted.size === 0,
        'attempted to roll back a graph commit but there were still changes',
      );

      // Re-throw the transform or resolution error originally seen by
      // `buildSubgraph`.
      throw error;
    }

    const added = new Map<string, Module<T>>();
    for (const path of delta.added) {
      added.set(path, nullthrows(this.dependencies.get(path)));
    }

    const modified = new Map<string, Module<T>>();
    for (const path of modifiedPathsInBaseGraph) {
      if (
        delta.touched.has(path) &&
        !delta.deleted.has(path) &&
        !delta.added.has(path)
      ) {
        modified.set(path, nullthrows(this.dependencies.get(path)));
      }
    }

    return {
      added,
      modified,
      deleted: delta.deleted,
    };
  }

  async initialTraverseDependencies(options: Options<T>): Promise<Result<T>> {
    const internalOptions = getInternalOptions(options);

    invariant(
      this.dependencies.size === 0,
      'initialTraverseDependencies called on nonempty graph',
    );

    this.#gc.color.clear();
    this.#gc.possibleCycleRoots.clear();
    this.#importBundleNodes.clear();

    for (const path of this.entryPoints) {
      // Each entry point implicitly has a refcount of 1, so mark them all black.
      this.#gc.color.set(path, 'black');
    }

    const delta = await this._buildDelta(this.entryPoints, internalOptions);

    if (delta.errors.size > 0) {
      // If we encountered any errors during traversal, throw one of them.
      // Since errors are encountered in a non-deterministic order, even on
      // fresh builds, it's valid to arbitrarily pick the first.
      throw delta.errors.values().next().value;
    }

    for (const path of this.entryPoints) {
      // We have already thrown on userland errors in the delta, so any error
      // encountered here would be exceptional and fatal.
      this._recursivelyCommitModule(path, delta, internalOptions);
    }

    this.reorderGraph({
      shallow: options.shallow,
    });

    return {
      added: this.dependencies,
      modified: new Map(),
      deleted: new Set(),
    };
  }

  async _buildDelta(
    pathsToVisit: $ReadOnlySet<string>,
    options: InternalOptions<T>,
    moduleFilter?: (path: string) => boolean,
  ): Promise<Delta<T>> {
    const subGraph = await buildSubgraph(pathsToVisit, this.#resolvedContexts, {
      resolve: options.resolve,
      transform: async (absolutePath, requireContext) => {
        options.onDependencyAdd();
        const result = await options.transform(absolutePath, requireContext);
        options.onDependencyAdded();
        return result;
      },
      shouldTraverse: (dependency: ResolvedDependency) => {
        if (options.shallow || isWeakOrLazy(dependency, options)) {
          return false;
        }
        return moduleFilter == null || moduleFilter(dependency.absolutePath);
      },
    });

    return {
      added: new Set(),
      touched: new Set(),
      deleted: new Set(),
      updatedModuleData: subGraph.moduleData,
      baseModuleData: new Map(),
      errors: subGraph.errors,
    };
  }

  _recursivelyCommitModule(
    path: string,
    delta: Delta<T>,
    options: InternalOptions<T>,
    commitOptions: $ReadOnly<{
      onlyRemove: boolean,
    }> = {onlyRemove: false},
  ): Module<T> {
    if (delta.errors.has(path)) {
      throw delta.errors.get(path);
    }

    const previousModule = this.dependencies.get(path);
    const currentModule: ModuleData<T> = nullthrows(
      delta.updatedModuleData.get(path) ?? delta.baseModuleData.get(path),
    );

    const previousDependencies = previousModule?.dependencies ?? new Map();
    const {
      dependencies: currentDependencies,
      resolvedContexts,
      ...transformResult
    } = currentModule;

    const nextModule = {
      ...(previousModule ?? {
        inverseDependencies: new CountingSet(),
        path,
      }),
      ...transformResult,
      dependencies: new Map(previousDependencies),
    };

    // Update the module information.
    this.dependencies.set(nextModule.path, nextModule);

    if (previousModule == null) {
      // If the module is not currently in the graph, it is either new or was
      // released earlier in the commit.
      if (delta.deleted.has(path)) {
        // Mark the addition by clearing a prior deletion.
        delta.deleted.delete(path);
      } else {
        // Mark the addition in the added set.
        delta.added.add(path);
      }
    }

    // Diff dependencies (1/3): remove dependencies that have changed or been removed.
    let dependenciesRemoved = false;
    for (const [key, prevDependency] of previousDependencies) {
      const curDependency = currentDependencies.get(key);
      if (
        !curDependency ||
        !dependenciesEqual(prevDependency, curDependency, options)
      ) {
        dependenciesRemoved = true;
        this._removeDependency(nextModule, key, prevDependency, delta, options);
      }
    }

    // Diff dependencies (2/3): add dependencies that have changed or been added.
    let dependenciesAdded = false;
    if (!commitOptions.onlyRemove) {
      for (const [key, curDependency] of currentDependencies) {
        const prevDependency = previousDependencies.get(key);
        if (
          !prevDependency ||
          !dependenciesEqual(prevDependency, curDependency, options)
        ) {
          dependenciesAdded = true;
          this._addDependency(
            nextModule,
            key,
            curDependency,
            resolvedContexts.get(key),
            delta,
            options,
          );
        }
      }
    }

    // Diff dependencies (3/3): detect changes in the ordering of dependency
    // keys, which must be committed even if no other changes were made.
    const previousDependencyKeys = [...previousDependencies.keys()];
    const dependencyKeysChangedOrReordered =
      currentDependencies.size !== previousDependencies.size ||
      [...currentDependencies.keys()].some(
        (currentKey, index) => currentKey !== previousDependencyKeys[index],
      );

    if (
      previousModule != null &&
      !transformOutputMayDiffer(previousModule, nextModule) &&
      !dependenciesRemoved &&
      !dependenciesAdded &&
      !dependencyKeysChangedOrReordered
    ) {
      // We have not operated on nextModule, so restore previousModule
      // to aid diffing. Don't add this path to delta.touched.
      this.dependencies.set(previousModule.path, previousModule);
      return previousModule;
    }

    delta.touched.add(path);

    // Replace dependencies with the correctly-ordered version, matching the
    // transform output. Because this assignment does not add or remove edges,
    // it does NOT invalidate any of the garbage collection state.

    // A subtractive pass only partially commits modules, so our dependencies
    // are not generally complete yet. We'll address ordering in the next pass
    // after processing additions.
    if (commitOptions.onlyRemove) {
      return nextModule;
    }

    // Catch obvious errors with a cheap assertion.
    invariant(
      nextModule.dependencies.size === currentDependencies.size,
      'Failed to add the correct dependencies',
    );

    nextModule.dependencies = new Map(currentDependencies);

    return nextModule;
  }

  _addDependency(
    parentModule: Module<T>,
    key: string,
    dependency: Dependency,
    requireContext: ?RequireContext,
    delta: Delta<T>,
    options: InternalOptions<T>,
  ): void {
    if (options.shallow) {
      // Don't add a node for the module if the graph is shallow (single-module).
    } else if (!isResolvedDependency(dependency)) {
      // If the dependency is a missing optional dependency, it has no node of
      // its own. We just need to add it to the parent's dependency map.
    } else if (dependency.data.data.asyncType === 'weak') {
      // Exclude weak dependencies from the bundle.
    } else if (options.lazy && dependency.data.data.asyncType != null) {
      // Don't add a node for the module if we are traversing async dependencies
      // lazily (and this is an async dependency). Instead, record it in
      // importBundleNodes.
      this._incrementImportBundleReference(dependency, parentModule);
    } else {
      // The module may already exist, in which case we just need to update some
      // bookkeeping instead of adding a new node to the graph.
      const path = dependency.absolutePath;
      let module = this.dependencies.get(path);

      if (!module) {
        try {
          module = this._recursivelyCommitModule(path, delta, options);
        } catch (error) {
          // If we couldn't add this module but it was added to the graph
          // before failing on a sub-dependency, it may be orphaned. Mark it as
          // a possible garbage root.
          const module = this.dependencies.get(path);
          if (module) {
            if (module.inverseDependencies.size > 0) {
              this._markAsPossibleCycleRoot(module);
            } else {
              this._releaseModule(module, delta, options);
            }
          }
          throw error;
        }
      }

      // We either added a new node to the graph, or we're updating an existing one.
      module.inverseDependencies.add(parentModule.path);
      this._markModuleInUse(module);
    }

    if (isResolvedDependency(dependency)) {
      const path = dependency.absolutePath;
      if (requireContext) {
        this.#resolvedContexts.set(path, requireContext);
      } else {
        // This dependency may have existed previously as a require.context -
        // clean it up.
        this.#resolvedContexts.delete(path);
      }
    }

    // Update the parent's dependency map unless we failed to add a dependency.
    // This means the parent's dependencies can get desynced from
    // inverseDependencies and the other fields in the case of lazy edges.
    // Not an optimal representation :(
    parentModule.dependencies.set(key, dependency);
  }

  _removeDependency(
    parentModule: Module<T>,
    key: string,
    dependency: Dependency,
    delta: Delta<T>,
    options: InternalOptions<T>,
  ): void {
    parentModule.dependencies.delete(key);

    if (
      !isResolvedDependency(dependency) ||
      dependency.data.data.asyncType === 'weak'
    ) {
      // Weak and unresolved dependencies are excluded from the bundle.
      return;
    }

    const {absolutePath} = dependency;

    const module = this.dependencies.get(absolutePath);

    if (options.lazy && dependency.data.data.asyncType != null) {
      this._decrementImportBundleReference(dependency, parentModule);
    } else if (module) {
      // Decrement inverseDependencies only if the dependency is not async,
      // mirroring the increment conditions in _addDependency.
      module.inverseDependencies.delete(parentModule.path);
    }

    if (!module) {
      return;
    }
    if (
      module.inverseDependencies.size > 0 ||
      this.entryPoints.has(absolutePath)
    ) {
      // The reference count has decreased, but not to zero.
      // NOTE: Each entry point implicitly has a refcount of 1.
      this._markAsPossibleCycleRoot(module);
    } else {
      // The reference count has decreased to zero.
      this._releaseModule(module, delta, options);
    }
  }

  /**
   * Collect a list of context modules which include a given file.
   */
  markModifiedContextModules(
    filePath: string,
    modifiedPaths: Set<string> | CountingSet<string>,
  ) {
    for (const [absolutePath, context] of this.#resolvedContexts) {
      if (
        !modifiedPaths.has(absolutePath) &&
        fileMatchesContext(filePath, context)
      ) {
        modifiedPaths.add(absolutePath);
      }
    }
  }

  /**
   * Gets the list of modules affected by the deletion of a given file. The
   * caller is expected to mark these modules as modified in the next call to
   * traverseDependencies. Note that the list may contain duplicates.
   */
  *getModifiedModulesForDeletedPath(filePath: string): Iterable<string> {
    yield* this.dependencies.get(filePath)?.inverseDependencies ?? [];
    yield* this.#importBundleNodes.get(filePath)?.inverseDependencies ?? [];
  }

  /**
   * Re-traverse the dependency graph in DFS order to reorder the modules and
   * guarantee the same order between runs. This method mutates the passed graph.
   */
  reorderGraph(options: {shallow: boolean, ...}): void {
    const orderedDependencies = new Map<string, Module<T>>();

    this.entryPoints.forEach((entryPoint: string) => {
      const mainModule = this.dependencies.get(entryPoint);

      if (!mainModule) {
        throw new ReferenceError(
          'Module not registered in graph: ' + entryPoint,
        );
      }

      this._reorderDependencies(mainModule, orderedDependencies, options);
    });
    this.dependencies.clear();
    for (const [key, dep] of orderedDependencies) {
      this.dependencies.set(key, dep);
    }
  }

  _reorderDependencies(
    module: Module<T>,
    orderedDependencies: Map<string, Module<T>>,
    options: {shallow: boolean, ...},
  ): void {
    if (module.path) {
      if (orderedDependencies.has(module.path)) {
        return;
      }

      orderedDependencies.set(module.path, module);
    }

    module.dependencies.forEach(dependency => {
      const path = dependency.absolutePath;
      if (path == null) {
        // If the dependency is not a missing optional dependency, it has no children to reorder.
        return;
      }
      const childModule = this.dependencies.get(path);

      if (!childModule) {
        if (dependency.data.data.asyncType != null || options.shallow) {
          return;
        } else {
          throw new ReferenceError('Module not registered in graph: ' + path);
        }
      }

      this._reorderDependencies(childModule, orderedDependencies, options);
    });
  }

  /** Garbage collection functions */

  // Add an entry to importBundleNodes (or record an inverse dependency of an existing one)
  _incrementImportBundleReference(
    dependency: ResolvedDependency,
    parentModule: Module<T>,
  ) {
    const {absolutePath} = dependency;
    const importBundleNode = this.#importBundleNodes.get(absolutePath) ?? {
      inverseDependencies: new CountingSet(),
    };
    importBundleNode.inverseDependencies.add(parentModule.path);
    this.#importBundleNodes.set(absolutePath, importBundleNode);
  }

  // Decrease the reference count of an entry in importBundleNodes (and delete it if necessary)
  _decrementImportBundleReference(
    dependency: ResolvedDependency,
    parentModule: Module<T>,
  ) {
    const {absolutePath} = dependency;

    const importBundleNode = nullthrows(
      this.#importBundleNodes.get(absolutePath),
    );
    invariant(
      importBundleNode.inverseDependencies.has(parentModule.path),
      'lazy: import bundle inverse references',
    );
    importBundleNode.inverseDependencies.delete(parentModule.path);
    if (importBundleNode.inverseDependencies.size === 0) {
      this.#importBundleNodes.delete(absolutePath);
    }
  }

  // Mark a module as in use (ref count >= 1)
  _markModuleInUse(module: Module<T>) {
    this.#gc.color.set(module.path, 'black');
  }

  // Iterate "children" of the given module - i.e. non-weak / async
  // dependencies having a corresponding inverse dependency.
  *_children(
    module: Module<T>,
    options: InternalOptions<T>,
  ): Iterator<Module<T>> {
    for (const dependency of module.dependencies.values()) {
      if (
        !isResolvedDependency(dependency) ||
        isWeakOrLazy(dependency, options)
      ) {
        continue;
      }
      yield nullthrows(this.dependencies.get(dependency.absolutePath));
    }
  }

  _moduleSnapshot(module: Module<T>): ModuleData<T> {
    const {dependencies, getSource, output, unstable_transformResultKey} =
      module;

    const resolvedContexts: Map<string, RequireContext> = new Map();
    for (const [key, dependency] of dependencies) {
      if (!isResolvedDependency(dependency)) {
        continue;
      }
      const resolvedContext = this.#resolvedContexts.get(
        dependency.absolutePath,
      );
      if (resolvedContext != null) {
        resolvedContexts.set(key, resolvedContext);
      }
    }
    return {
      dependencies: new Map(dependencies),
      resolvedContexts,
      getSource,
      output,
      unstable_transformResultKey,
    };
  }

  // Delete an unreachable module (and its outbound edges) from the graph
  // immediately.
  // Called when the reference count of a module has reached 0.
  _releaseModule(
    module: Module<T>,
    delta: Delta<T>,
    options: InternalOptions<T>,
  ) {
    if (
      !delta.updatedModuleData.has(module.path) &&
      !delta.baseModuleData.has(module.path)
    ) {
      // Before releasing a module, take a snapshot of the data we might need
      // to reintroduce it to the graph later in this commit. As it is not
      // already present in updatedModuleData we can infer it has not been modified,
      // so the transform output and dependencies we copy here are current.
      delta.baseModuleData.set(module.path, this._moduleSnapshot(module));
    }

    for (const [key, dependency] of module.dependencies) {
      if (!isResolvedDependency(dependency)) {
        // If the dependency is not a missing optional dependency, it has no children to remove.
        continue;
      }
      this._removeDependency(module, key, dependency, delta, options);
    }
    this.#gc.color.set(module.path, 'black');
    this._freeModule(module, delta);
  }

  // Delete an unreachable module from the graph.
  _freeModule(module: Module<T>, delta: Delta<T>) {
    if (delta.added.has(module.path)) {
      // Mark the deletion by clearing a prior addition.
      delta.added.delete(module.path);
    } else {
      // Mark the deletion in the deleted set.
      delta.deleted.add(module.path);
    }

    // This module is not used anywhere else! We can clear it from the bundle.
    // Clean up all the state associated with this module in order to correctly
    // re-add it if we encounter it again.
    this.dependencies.delete(module.path);
    this.#gc.possibleCycleRoots.delete(module.path);
    this.#gc.color.delete(module.path);
    this.#resolvedContexts.delete(module.path);
  }

  // Mark a module as a possible cycle root
  _markAsPossibleCycleRoot(module: Module<T>) {
    if (this.#gc.color.get(module.path) !== 'purple') {
      this.#gc.color.set(module.path, 'purple');
      this.#gc.possibleCycleRoots.add(module.path);
    }
  }

  // Collect any unreachable cycles in the graph.
  _collectCycles(delta: Delta<T>, options: InternalOptions<T>) {
    // Mark recursively from roots (trial deletion)
    for (const path of this.#gc.possibleCycleRoots) {
      const module = nullthrows(this.dependencies.get(path));
      const color = nullthrows(this.#gc.color.get(path));
      if (color === 'purple') {
        this._markGray(module, options);
      } else {
        this.#gc.possibleCycleRoots.delete(path);
        if (
          color === 'black' &&
          module.inverseDependencies.size === 0 &&
          !this.entryPoints.has(path)
        ) {
          this._freeModule(module, delta);
        }
      }
    }
    // Scan recursively from roots (undo unsuccessful trial deletions)
    for (const path of this.#gc.possibleCycleRoots) {
      const module = nullthrows(this.dependencies.get(path));
      this._scan(module, options);
    }
    // Collect recursively from roots (free unreachable cycles)
    for (const path of this.#gc.possibleCycleRoots) {
      this.#gc.possibleCycleRoots.delete(path);
      const module = nullthrows(this.dependencies.get(path));
      this._collectWhite(module, delta);
    }
  }

  _markGray(module: Module<T>, options: InternalOptions<T>) {
    const color = nullthrows(this.#gc.color.get(module.path));
    if (color !== 'gray') {
      this.#gc.color.set(module.path, 'gray');
      for (const childModule of this._children(module, options)) {
        // The inverse dependency will be restored during the scan phase if this module remains live.
        childModule.inverseDependencies.delete(module.path);
        this._markGray(childModule, options);
      }
    }
  }

  _scan(module: Module<T>, options: InternalOptions<T>) {
    const color = nullthrows(this.#gc.color.get(module.path));
    if (color === 'gray') {
      if (
        module.inverseDependencies.size > 0 ||
        this.entryPoints.has(module.path)
      ) {
        this._scanBlack(module, options);
      } else {
        this.#gc.color.set(module.path, 'white');
        for (const childModule of this._children(module, options)) {
          this._scan(childModule, options);
        }
      }
    }
  }

  _scanBlack(module: Module<T>, options: InternalOptions<T>) {
    this.#gc.color.set(module.path, 'black');
    for (const childModule of this._children(module, options)) {
      // The inverse dependency must have been deleted during the mark phase.
      childModule.inverseDependencies.add(module.path);
      const childColor = nullthrows(this.#gc.color.get(childModule.path));
      if (childColor !== 'black') {
        this._scanBlack(childModule, options);
      }
    }
  }

  _collectWhite(module: Module<T>, delta: Delta<T>) {
    const color = nullthrows(this.#gc.color.get(module.path));
    if (color === 'white' && !this.#gc.possibleCycleRoots.has(module.path)) {
      this.#gc.color.set(module.path, 'black');
      for (const dependency of module.dependencies.values()) {
        if (!isResolvedDependency(dependency)) {
          continue;
        }
        const childModule = this.dependencies.get(dependency.absolutePath);
        // The child may already have been collected.
        if (childModule) {
          this._collectWhite(childModule, delta);
        }
      }
      this._freeModule(module, delta);
    }
  }

  /** End of garbage collection functions */
}

function dependenciesEqual(
  a: Dependency,
  b: Dependency,
  options: $ReadOnly<{lazy: boolean, ...}>,
): boolean {
  return (
    a === b ||
    (a.absolutePath === b.absolutePath &&
      (!options.lazy || a.data.data.asyncType === b.data.data.asyncType) &&
      contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams))
  );
}

function contextParamsEqual(
  a: ?RequireContextParams,
  b: ?RequireContextParams,
): boolean {
  return (
    a === b ||
    (a == null && b == null) ||
    (a != null &&
      b != null &&
      a.recursive === b.recursive &&
      a.filter.pattern === b.filter.pattern &&
      a.filter.flags === b.filter.flags &&
      a.mode === b.mode)
  );
}

function transformOutputMayDiffer<T>(a: Module<T>, b: Module<T>): boolean {
  return (
    a.unstable_transformResultKey == null ||
    a.unstable_transformResultKey !== b.unstable_transformResultKey
  );
}

Выполнить команду


Для локальной разработки. Не используйте в интернете!