生成模块依赖图
构建阶段的第一步是生成模块依赖图。
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> {}
}