调用 rollup 发生了什么
在源码的 src/rollup/rollup.ts 文件中找到 rollup 的定义:
export default function rollup(rawInputOptions: RollupOptions): Promise<RollupBuild> {
return rollupInternal(rawInputOptions, null);
}
rollup 方法就是将用户传入的 rawInputOptions 原封不动的传给了 rollupInternal 方法,并且第二个参数的值为 null(watcher 参数我们在源码分析阶段可以忽略它,把关注点放在主线逻辑上这样可以减少干扰项。感兴趣的同学可以自行查看源码进行分析)。
- rawInputOptions 就是用户传入的参数对象,它遵守 InputOptions 接口的约定。InputOptions 接口定义在 src/rollup/types.d.ts 文件中:
export interface InputOptions {
acorn?: Record<string, unknown>; //对象类型。acorn是一个js语法解析器,非必传。代表用户可以使用自定义的语法解析器,如果不传则默认使用acorn解析器
acornInjectPlugins?: (() => unknown)[] | (() => unknown); //InjectPlugins
cache?: false | RollupCache; //缓存
context?: string; //context
experimentalCacheExpiry?: number; //graph.pluginCache ,默认 最大值为10。这个是实验性质的功能
external?: ExternalOption; //自定义判断是否为外部模块的函数,非必传
/** @deprecated Use the "inlineDynamicImports" output option instead. */
inlineDynamicImports?: boolean;
input?: InputOption; //打包入口模块,可以是 string | string[] | { [entryAlias: string]: string } 这几种类型
makeAbsoluteExternalsRelative?: boolean | 'ifRelativeSource';
/** @deprecated Use the "manualChunks" output option instead. */
manualChunks?: ManualChunksOption; //代码块
maxParallelFileOps?: number; //最大队列执行数,默认为20
/** @deprecated Use the "maxParallelFileOps" option instead. */
maxParallelFileReads?: number;
moduleContext?: ((id: string) => string | NullValue) | { [id: string]: string }; //模块id默认是undefined
onwarn?: WarningHandlerWithDefault; //告警函数
perf?: boolean; //显示性能计时
plugins?: InputPluginOption; //自定义插件
preserveEntrySignatures?: PreserveEntrySignaturesOption; //如果值为false则不保留exports代码,默认值为"exports-only"
/** @deprecated Use the "preserveModules" output option instead. */
preserveModules?: boolean; //保留模块结构,默认值undefined
preserveSymlinks?: boolean; //默认值false
shimMissingExports?: boolean; //默认值false
strictDeprecations?: boolean; //默认值false
treeshake?: boolean | TreeshakingPreset | TreeshakingOptions; //treeshake 配置
watch?: WatcherOptions | false;
}
我们接着来看 rollupInternal 方法,rollupInternal 内部主要干了 4 件事:
- 首先会调用 getInputOptions 方法将用户传入的 inputOptions 与 rollup 内部默认的 InputOptions 进行合并操作拿到最终的 inputOptions 配置。
- 接着实例化 Graph, 在 new Graph 的时候主要是初始化 this.pluginDriver、this.acornParser、this.moduleLoader、this.fileOperationQueue、生成全局作用域等等属性和方法。
- 然后调用 graph.build() 方法执行打包逻辑。
- 最后返回了 RollupBuild 对象。
这个 RollupBuild 对象包含了 cache、close、closed、generate、getTimings、watchFiles、write 等属性或方法。cache 保存了原始的模块相关的信息,例如打包前的原始代码。 watchFiles 保存了被 rollup 处理过的模块 id 信息。RollupBuild 对象中的 write 方法可以将最终的代码写入到指定的文件里。
export async function rollupInternal(
rawInputOptions: RollupOptions,
watcher: RollupWatcher | null
): Promise<RollupBuild> {
//合并用户传入的inputOptions
const { options: inputOptions, unsetOptions: unsetInputOptions } = await getInputOptions(
rawInputOptions,
watcher !== null
);
//实例化Graph,这个 Graph 保存了所有的 module 。
const graph = new Graph(inputOptions, watcher);
//...
await catchUnfinishedHookActions(graph.pluginDriver, async () => {
try {
//...
await graph.build();
} catch (error_: any) {
//...
});
//...
const result: RollupBuild = {
cache: useCache ? graph.getCache() : undefined,
async close() {
if (result.closed) return;
result.closed = true;
await graph.pluginDriver.hookParallel('closeBundle', []);
},
closed: false,
async generate(rawOutputOptions: OutputOptions) {
if (result.closed) return error(errorAlreadyClosed());
return handleGenerateWrite(false, inputOptions, unsetInputOptions, rawOutputOptions, graph);
},
watchFiles: Object.keys(graph.watchFiles),
async write(rawOutputOptions: OutputOptions) {
if (result.closed) return error(errorAlreadyClosed());
return handleGenerateWrite(true, inputOptions, unsetInputOptions, rawOutputOptions, graph);
}
};
if (inputOptions.perf) result.getTimings = getTimings;
return result;
}
总结
rollup 执行打包的逻辑非常清楚,开发者把不同功能的逻辑进行拆分到一些单独的函数或者 class 中,从而让主线逻辑非常清晰。这种方式其实就是“解耦”的思想。在使用 rollup 的配置上,纵观一些框架、库的设计几乎都是类似的,在其内部预先定义了默认的配置,然后在初始化的时候传入自定义配置。最后在它的内部会帮我进行合并配置来达到不同的需求的目的。我们在学习源码的过程中,不仅需要知道为什么,而且还要学习作者一些优秀的编程思想,这才是我们学习源码的目的所在。
到目前为止我们学习了 rollup 的打包的主要过程,那么接下来我们顺着主线逻辑继续深入代码一探究竟吧!
rollup 揭秘相关文章
- rollup 技术揭秘系列一 准备篇(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列二 源码目录结构及打包入口分析(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列三 rollup 函数(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列四 graph.build(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列五 构建依赖图谱(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列六 模块排序(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列七 includeStatements(可能是全网最系统性的 rollup 源码分析文章
- rollup 技术揭秘系列八 node.hasEffects(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列九 module.include()(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十 includeStatements 总结(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十一 rollup 打包配置选项整理(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十二 handleGenerateWrite(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十三 bundle.generate(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十四 renderChunks(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十五 renderModules(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十六 Rollup 插件开发指南(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十七 rollup-cli 的开发(可能是全网最系统性的 rollup 源码分析文章)
- rollup 技术揭秘系列十八 Rollup 打包流程示意图(可能是全网最系统性的 rollup 源码分析文章)