Rollup的插件机制
类似于Vue、React,在Rollup中也有一套自己的生命周期,从打包开始到产物输出,在每个周期都会执行特有的钩子函数(Hook)。
Rollup 整体构建阶段
Rollup主要经历了Build和Output两个阶段
对于一次完整的构建过程而言,Rollup会先进入到Build阶段,解析各模块的内容及依赖关系,然后进入Output阶段,完成打包及输出。在不同的阶段,插件会有不同的工作流程。
插件Hook分类
根据构建阶段分类
Rollup的主要构建有Build和Output两大阶段,根据构建阶段分类,插件的Hook也可分为Build Hook和Output Hook
Build Hook就是在Build阶段执行的钩子函数,在这个阶段主要进行模块代码的转换、AST解析以及模块依赖的解析,那么这个阶段的Hook对于代码的操作粒度一般为模块级别,也就是单文件级别Output Hook主要进行代码的打包,操作粒度一般为chunk级别(一个chunk通常指很多文件打包到一起的产物)。
根据Hook的执行方式分类
根据Hook的执行方式可以分为Async、Sync、Parallel、Sequential、First这几类
Async & Sync
Async和Sync钩子函数分别代表异步和同步的钩子函数,Async钩子需要返回一个Promise,否则该钩子会被认为是Sync同步钩子
First
如果多个插件都实现了该类型的Hook,那Hook将按顺序执行,直到其中一个Hook返回非null或者非undefined的值。
TIP
- 比如
resolveId这个Hook,如果多个插件都实现了这个hook,其中某个插件的resolveId函数返回了一个路径,将停止执行后续插件的resolveId方法
Sequential
如果好几个插件实现了该Hook,它们将会按照指定的顺序执行,如果当前Hook是
异步的,后续的Hook也要等待当前Hook的状态为resolved后才能执行这种类型的Hook也适用于当前插件的Hook依赖上一插件Hook的处理结果,要等上一个插件Hook处理完后才返回处理结果给当前插件的Hook。比如
transform钩子函数
Parallel
- 该类型的Hook是指可以并行执行的Hook,如果
多个插件都实现了该类型的钩子函数,那么这些钩子函数可以同时执行。就算当前Hook是异步的,其他该类型的Hook也不会等待该Hook执行完毕,而是一起执行
TIP
比如在Build阶段的buildStart钩子,它的执行时机是在构建刚开始的时候,每个插件声明的buildStart函数可以做一些状态初始化操作,但这些插件之间的操作并不是相互依赖,是可以并发执行的,从而提升构建性能。反之,对于需要依赖其他插件处理结果的情况就不适合用Parallel钩子了,比如transform钩子函数。
拓展
Hook可以是一个函数,也可以是一个对象,如果是一个对象,则对象中必须要有一个handler函数,该函数其实相当于实际的Hook,使用对象时,可以在对象中添加一些可选项,如下:
- order:
"pre" | "post" | null
如果有多个Hook,可以通过order选项来设置Hook执行优先级,pre先执行,post后执行,如果不设置或者设置为null,将会在用户指定的位置执行
export default function resolveFirst() {
return {
name: 'resolve-first',
resolveId: {
order: 'pre',
handler(source) {
if (source === 'external') {
return { id: source, external: true };
}
return null;
}
}
};
}
- sequential:
boolean
该选项只能用于Parallel(并行钩子)类型的Hook
当某个Hook使用该选项后,此Hook将会等待前面所有插件的该Hook函数都并行执行完毕后,再执行该Hook函数,然后在并行执行剩余插件的该Hook
例如: 有5个插件A,B,C,D,E,都实现了相同的Parallel Hook,其中C插件中设置了
sequential: true,那么Rollup将会先并发执行A,B插件的该Hook,然后执行C插件的该Hook,当C执行完毕后,再并发执行D,E插件的该Hook
插件工作流
Build阶段工作流
在Build阶段会执行相关的Build Hooks,是由rollup.rollup(inputOptions)函数触发,构建阶段的第一个Hook是options,最后一个Hook是buildEnd,当构建过程出错时,会执行closeBundle
Build流程如下图:

TIP
在watch模式下,Rollup内部会初始化一个watcher对象,当文件内容发生变化时,watcher对象会同时触发watchChange Hook,对项目进行重新构建。此外,在打包结束时,Rollup会自动清除watcher对象,并调用closeWatcher Hook
Build阶段流程如下:
执行
optionsHook,替换或操作传递给rollup.rollup的配置项调用
buildStartHook,正式开始构建流程,如果只是查看配置项,推荐使用buildStart调用
resolveIdHook,开始解析路径(从input配置指定的入口文件开始解析)调用
loadHook,加载模块内容调用
shouldTransformCachedModuleHook,这个钩子可以用来找出缓存了哪些模块,并访问它们缓存的元信息。如果使用了Rollup缓存(例如
watch模式下或通过JavaScript API显式地使用),如果loadHook加载的代码与缓存副本中的相同则跳过transformHook并使用模块缓存的代码,然后执行moduleParsedHook如果
shouldTransformCachedModuleHook返回true,则从缓存中删除此模块并重新执行transformHook
调用
transformHook,对模块内容进行转换,例如babel转译调用
moduleParsedHook(Rollup拿到最后的模块内容,进行AST分析,得到所有的import内容,然后执行此Hook),执行该Hook后有以下两种情况:如果是
普通的import,则跳到resolveIdHook流程如果是
动态import,则执行resolveDynamicImportHook解析路径,如果路径解析成功,就跳到loadHook流程,否则就跳到resolveIdHook流程
直到所有的
import都解析完毕,Rollup会执行buildEndHook,Build阶段结束
TIP
Rollup在解析路径的时候,即执行resolveId/resolveDynamicImport Hook的时候,如果该路径被标记为external(外部模块),表明该路径内容不用进行Rollup打包,则直接跳转到buildEnd Hook,不再执行load/transform/resolveId等Hook
Output阶段工作流
Output generation hooks可以提供关于生成的bundle的信息并在生成完成后修改构建。它们的工作方式和类型与Build Hook相同,但是每次调用bundle.generate(outputOptions)或bundle.write(outputOptions)时会单独调用,只会调用其中一个
Output流程如下图:

Output阶段流程如下:
执行所有插件的
outputoptionsHook,对output配置进行转换并发执行
renderStartHook,正式开始打包从入口模块开始扫描,针对
动态import语句执行renderDynamicImport钩子,来自定义动态import的内容判断是否遇到
i语句mport.meta 如果没有遇到
i语句,则并发执行所有插件的mport.meta banner、footer、intro、outroHook,向打包产物的固定位置(比如头部和尾部)插入一些自定义的内容,比如协议声明内容、项目介绍等等如果遇到
i语句:mport.meta 对于
i语句调用mport.meta.url resolveFileUrl来自定义url解析逻辑对于
其他i属性,则调用mport.meta resolveImportMeta来进行自定义的解析。
接下来Rollup会生成所有
chunk的内容,针对每个chunk会依次调用插件的renderChunk方法进行自定义操作,在这里就可以直接操作打包产物了。接着调用
augmentChunkHashHook,用于扩充单个chunks的hash值,如果该函数返回false将不会修改hash值随后会调用
generateBundleHook,这个Hook的入参里面会包含所有的打包产物信息,包括chunk(打包后的代码)、asset(最终的静态资源文件)。我们可以在这里删除一些chunk或者asset,最终这些内容将不会作为产物输出。rollup.rollup方法会返回一个bundle对象,这个对象是包含generate和write两个方法- 如果输出是通过
bundle.generate(...)方法成功生成的,那么最后一个Hook就是generateBundleHook - 如果输出是通过
bundle.write(...)方法成功生成的,那么最后一个Hook就是writeBundleHook - 如果输出过程中出错,会触发
renderErrorHook,然后执行closeBundleHook 结束打包
- 如果输出是通过
TIP
- bundle.generate(...)和bundle.write(...)的区别是
write方法会将代码写入到磁盘中,同时触发writeBundleHook,而generateBundleHook执行的时候,产物还并没有输出, - 以上两个Hook的顺序为
generateBundle => 输出产物到磁盘 => writeBundle