Skip to content

生成模块依赖图

构建阶段的第一步是生成模块依赖图。

ts
class Graph {
  async build(): Promise<void> {
    timeStart('generate module graph', 2);
    await this.generateModuleGraph();
    timeEnd('generate module graph', 2);

    timeStart('sort and bind modules', 2);
    this.phase = BuildPhase.ANALYSE;
    this.sortModules();
    timeEnd('sort and bind modules', 2);

    timeStart('mark included statements', 2);
    this.includeStatements();
    timeEnd('mark included statements', 2);

    this.phase = BuildPhase.GENERATE;
  }
}

Rollup 会通过用户配置的 input 选项(即 options.input)来确定入口模块,实例化 Module 类,获取路径对应的模块信息,然后递归地查找所有依赖的模块,最终生成一个模块依赖图。

ts
function normalizeEntryModules(
  entryModules: readonly string[] | Record<string, string>
  ): UnresolvedModule[] {
  if (Array.isArray(entryModules)) {
    return entryModules.map(id => ({
      fileName: null,
      id,
      implicitlyLoadedAfter: [],
      importer: undefined,
      name: null
    }));
  }
  return Object.entries(entryModules).map(([name, id]) => ({
    fileName: null,
    id,
    implicitlyLoadedAfter: [],
    importer: undefined,
    name
  }));
}
class Graph {
  private async generateModuleGraph(): Promise<void> {
    ({ entryModules: this.entryModules, implicitEntryModules: this.implicitEntryModules } =
      await this.moduleLoader.addEntryModules(normalizeEntryModules(this.options.input), true));
    if (this.entryModules.length === 0) {
      throw new Error('You must supply options.input to rollup');
    }
    for (const module of this.modulesById.values()) {
      module.cacheInfoGetters();
      if (module instanceof Module) {
        this.modules.push(module);
      } else {
        this.externalModules.push(module);
      }
    }
  }
}

当所有的入口模块及其所有的依赖项模块均实例化结束后,Graph 实例会收集其中所有的模块实例,存储在 modules 数组中。同时调用 Module 类的 cacheInfoGetters 方法,缓存 Module 实例的属性访问。

ts
function cacheObjectGetters<T, K extends PropertyKey = keyof T>(
  object: T,
  getterProperties: K[]
) {
  for (const property of getterProperties) {
    const propertyGetter = Object.getOwnPropertyDescriptor(
      object,
      property
    )!.get!;
    Object.defineProperty(object, property, {
      get() {
        const value = propertyGetter.call(object);
        // This replaces the getter with a fixed value for subsequent calls
        Object.defineProperty(object, property, { value });
        return value;
      }
    });
  }
}
class Module {
  cacheInfoGetters(): void {
    cacheObjectGetters(this.info, [
      'dynamicallyImportedIdResolutions',
      'dynamicallyImportedIds',
      'dynamicImporters',
      'exportedBindings',
      'exports',
      'hasDefaultExport',
      'implicitlyLoadedAfterOneOf',
      'implicitlyLoadedBefore',
      'importedIdResolutions',
      'importedIds',
      'importers'
    ]);
  }
}

对于 Module 实例的创建和解析是通过 moduleLoader 实例的 addEntryModules 方法实现的。moduleLoader 实例的创建是在 Graph 类实例化时。

ts
class Graph {
  readonly moduleLoader: ModuleLoader;
  readonly modulesById = new Map<string, Module | ExternalModule>();
  readonly pluginDriver: PluginDriver;

  constructor(
    private readonly options: NormalizedInputOptions,
    watcher: RollupWatcher | null
  ) {
    this.pluginDriver = new PluginDriver(
      this,
      options,
      options.plugins,
      this.pluginCache
    );
    this.moduleLoader = new ModuleLoader(
      this,
      this.modulesById,
      this.options,
      this.pluginDriver
    );
  }
}

Graph 实例是全局唯一的,Rollup 在构建前阶段会创建 Graph 实例,并将 inputOptions 实例和 watcher 实例传递给 Graph 实例。

ts
async function rollupInternal(
  rawInputOptions: RollupOptions,
  watcher: RollupWatcher | null
): Promise<RollupBuild> {
  const graph = new Graph(inputOptions, watcher);
  await catchUnfinishedHookActions(graph.pluginDriver, async () => {
    try {
      timeStart('initialize', 2);
      await graph.pluginDriver.hookParallel('buildStart', [inputOptions]);
      timeEnd('initialize', 2);
      await graph.build();
    } catch (error_: any) {
      const watchFiles = Object.keys(graph.watchFiles);
      if (watchFiles.length > 0) {
        error_.watchFiles = watchFiles;
      }
      await graph.pluginDriver.hookParallel('buildEnd', [error_]);
      await graph.pluginDriver.hookParallel('closeBundle', []);
      throw error_;
    }
    await graph.pluginDriver.hookParallel('buildEnd', []);
  });
}

ModuleLoader 类是 Rollup 的核心类之一,负责模块的加载和解析。Graph 类在实例化时会创建一个唯一的 ModuleLoader 实例,并将自身实例、modulesById 实例、options 实例和 pluginDriver 实例传递给 moduleLoader 属性。同时插件系统是 Rollup 的核心功能之一,Vite 的插件系统也借鉴了 Rollup 的实现,可以看到 Graph 类在初始化阶段通过 pluginDriver 类来初始化插件系统。

moduleLoader 实例的 addEntryModules 方法会根据 options.input 配置项生成入口模块,并递归地查找所有依赖的模块,最终生成一个模块依赖图。

ts
class ModuleLoader {
  async addEntryModules(
    entryModules: UnresolvedModule[],
    isInitialBuild: boolean
  ): Promise<void> {}
}

Contributors

Changelog

Discuss

Released under the CC BY-SA 4.0 License. (2619af4)