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阶段流程如下:
执行
options
Hook,替换
或操作
传递给rollup.rollup
的配置项调用
buildStart
Hook,正式开始构建流程,如果只是查看配置项,推荐使用buildStart调用
resolveId
Hook,开始解析路径(从input配置指定的入口文件开始解析)调用
load
Hook,加载模块内容调用
shouldTransformCachedModule
Hook,这个钩子可以用来找出缓存了哪些模块,并访问它们缓存的元信息。如果使用了Rollup缓存(例如
watch
模式下或通过JavaScript API
显式地使用),如果load
Hook加载的代码与缓存副本中的相同则跳过transform
Hook并使用模块缓存的代码,然后执行moduleParsed
Hook如果
shouldTransformCachedModule
Hook返回true
,则从缓存中删除此模块并重新执行transform
Hook
调用
transform
Hook,对模块内容进行转换,例如babel
转译调用
moduleParsed
Hook(Rollup拿到最后的模块内容,进行AST
分析,得到所有的import
内容,然后执行此Hook),执行该Hook后有以下两种情况:如果是
普通的import
,则跳到resolveId
Hook流程如果是
动态import
,则执行resolveDynamicImport
Hook解析路径,如果路径解析成功,就跳到load
Hook流程,否则就跳到resolveId
Hook流程
直到所有的
import
都解析完毕,Rollup会执行buildEnd
Hook,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阶段流程如下:
执行所有插件的
outputoptions
Hook,对output配置进行转换并发执行
renderStart
Hook,正式开始打包从入口模块开始扫描,针对
动态import
语句执行renderDynamicImport
钩子,来自定义动态import的内容判断是否遇到
i
语句mport.meta 如果没有遇到
i
语句,则并发执行所有插件的mport.meta banner
、footer
、intro
、outro
Hook,向打包产物的固定位置(比如头部和尾部)插入一些自定义的内容
,比如协议声明内容、项目介绍等等如果遇到
i
语句:mport.meta 对于
i
语句调用mport.meta.url resolveFileUrl
来自定义url解析逻辑对于
其他i
属性,则调用mport.meta resolveImportMeta
来进行自定义的解析。
接下来Rollup会生成所有
chunk
的内容,针对每个chunk会依次调用插件的renderChunk
方法进行自定义操作,在这里就可以直接操作打包产物了。接着调用
augmentChunkHash
Hook,用于扩充单个chunks的hash值,如果该函数返回false
将不会修改hash值随后会调用
generateBundle
Hook,这个Hook的入参里面会包含所有的打包产物信息,包括chunk(打包后的代码)
、asset(最终的静态资源文件)
。我们可以在这里删除一些chunk
或者asset
,最终这些内容将不会作为产物输出。rollup.rollup
方法会返回一个bundle
对象,这个对象是包含generate
和write
两个方法- 如果输出是通过
bundle.generate(...)
方法成功生成的,那么最后一个Hook就是generateBundle
Hook - 如果输出是通过
bundle.write(...)
方法成功生成的,那么最后一个Hook就是writeBundle
Hook - 如果输出过程中出错,会触发
renderError
Hook,然后执行closeBundle
Hook 结束打包
- 如果输出是通过
TIP
- bundle.generate(...)和bundle.write(...)的区别是
write
方法会将代码写入到磁盘中,同时触发writeBundle
Hook,而generateBundle
Hook执行的时候,产物还并没有输出, - 以上两个Hook的顺序为
generateBundle => 输出产物到磁盘 => writeBundle