Skip to content
On this page

Rollup的插件机制

类似于Vue、React,在Rollup中也有一套自己的生命周期,从打包开始到产物输出,在每个周期都会执行特有的钩子函数(Hook)

Rollup 整体构建阶段

Rollup主要经历了BuildOutput两个阶段

对于一次完整的构建过程而言,Rollup会先进入到Build阶段,解析各模块的内容依赖关系,然后进入Output阶段,完成打包及输出。在不同的阶段,插件会有不同的工作流程。

插件Hook分类

根据构建阶段分类

Rollup的主要构建有BuildOutput两大阶段,根据构建阶段分类,插件的Hook也可分为Build HookOutput Hook

  • Build Hook就是在Build阶段执行的钩子函数,在这个阶段主要进行模块代码的转换AST解析以及模块依赖的解析,那么这个阶段的Hook对于代码的操作粒度一般为模块级别,也就是单文件级别

  • Output Hook主要进行代码的打包,操作粒度一般为chunk级别(一个chunk通常指很多文件打包到一起的产物)。

根据Hook的执行方式分类

根据Hook的执行方式可以分为AsyncSyncParallelSequentialFirst这几类

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,将会在用户指定的位置执行

javascript
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的内容

  • 判断是否遇到import.meta语句

    • 如果没有遇到import.meta语句,则并发执行所有插件的bannerfooterintrooutro Hook,向打包产物的固定位置(比如头部和尾部)插入一些自定义的内容,比如协议声明内容、项目介绍等等

    • 如果遇到import.meta语句:

      • 对于import.meta.url语句调用resolveFileUrl来自定义url解析逻辑

      • 对于其他import.meta属性,则调用resolveImportMeta来进行自定义的解析。

  • 接下来Rollup会生成所有chunk的内容,针对每个chunk会依次调用插件的renderChunk方法进行自定义操作,在这里就可以直接操作打包产物了。

  • 接着调用augmentChunkHash Hook,用于扩充单个chunks的hash值,如果该函数返回false将不会修改hash值

  • 随后会调用generateBundle Hook,这个Hook的入参里面会包含所有的打包产物信息,包括chunk(打包后的代码)asset(最终的静态资源文件)。我们可以在这里删除一些chunk或者asset,最终这些内容将不会作为产物输出。

  • rollup.rollup方法会返回一个bundle对象,这个对象是包含generatewrite两个方法

    • 如果输出是通过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