钩子
一、认识
1.1 构建阶段
插件的各种 Hook
可以根据这两个构建阶段分为两类: Build Hook
与 Output Hook
。
-
Build Hook
: 即在Build阶段执行的钩子函数,在这个阶段主要进行模块代码的转换、AST
解析以及模块依赖的解析,那么这个阶段的Hook
对于代码的操作粒度一般为模块级别,也就是单文件级别。 -
Output Hook
: (官方称为Output Generation Hook
),则主要进行代码的打包,对于代码而言,操作粒度一般为chunk
级别(一个chunk
通常指很多文件打包到一起的产物)。
1.2 执行方式
除了根据构建阶段可以将 Rollup
插件进行分类,根据不同的 Hook
执行方式也会有不同的分类,主要包括Async
、Sync
、Parallel
、Sequential
、First
这五种。在后文中我们将接触各种各样的插件 Hook
,但无论哪个 Hook
都离不开这五种执行方式。
-
Async & Sync
: 首先是Async
和Sync
钩子函数,两者其实是相对的,分别代表异步和同步的钩子函数,两者最大的区别在于同步钩子里面不能有异步逻辑,而异步钩子可以有。 -
Parallel
: 这里指并行的钩子函数。如果有多个插件实现了这个钩子的逻辑,一旦有钩子函数是异步逻辑,则并发执行钩子函数,不会等待当前钩子完成(底层使用Promise.all
)。比如对于Build
阶段的buildStart
钩子,它的执行时机其实是在构建刚开始的时候,各个插件可以在这个钩子当中做一些状态的初始化操作,但其实插件之间的操作并不是相互依赖的,也就是可以并发执行,从而提升构建性能。反之,对于需要依赖其他插件处理结果的情况就不适合用Parallel
钩子了,比如transform
。 -
Sequential
: 指串行的钩子函数。这种Hook
往往适用于插件间处理结果相互依赖的情况,前一个插件Hook
的返回值作为后续插件的入参,这种情况就需要等待前一个插件执行完Hook
,获得其执行结果,然后才能进行下一个插件相应Hook
的调用,如transform
。 -
First
: 如果有多个插件实现了这个Hook
,那么Hook
将依次运行,直到返回一个非null
或非undefined
的值为止。比较典型的Hook
是resolveId
,一旦有插件的resolveId
返回了一个路径,将停止执行后续插件的resolveId
逻辑。
1.3 this 上下文
二、options
首先经历 options
钩子进行配置的转换,得到处理后的配置对象。
三、buildStart
**Rollup
会调用 buildStart
钩子,正式开始构建流程。
四、resolveId
Rollup
先进入到 resolveId
钩子中解析文件路径。(从 input
配置指定的入口文件开始)。resolveId
钩子一般用来解析模块路径,为Async + First
类型即异步优先的钩子。
4.1 语法
function Plugin(options){
return {
name: "rollup-plugin-xxx",
// 传入三个参数,当前模块路径、引用当前模块的模块路径、其余参数
resolveId(importee, importer, resolveOptions){
}
}
}
-
参数:
-
importee
: 当前模块路径 -
importer
: 引用当前模块的模块路径 -
resolveOptions
: 解析参数
-
-
返回值: 返回值可以是
null
、string
或者一个对象-
返回值为
null
时,会默认交给下一个插件的resolveId
钩子处理。 -
返回值为
string
时,则停止后续插件的处理。这里为了让替换后的路径能被其他插件处理,特意调用了this.resolve
来交给其它插件处理,否则将不会进入到其它插件的处理。 -
返回值为一个对象,也会停止后续插件的处理,不过这个对象就可以包含更多的信息了,包括解析后的路径、是否被
enternal
、是否需要tree-shaking
等等,不过大部分情况下返回一个string
就够用了。
-
五、load
Rollup
通过调用 load
钩子加载模块内容。load
为 Async + First
类型,即异步优先的钩子,和 resolveId
类似。它的作用是通过 resolveId
解析后的路径来加载模块内容。
5.1 语法
function Plugin(options){
return {
name: "rollup-plugin-xxx",
load(id){
}
}
}
-
参数:
id
:
-
返回值: 返回值一般是
null
、string
或者一个对象-
如果返回值为
null
,则交给下一个插件处理 -
如果返回值为
string
或者对象,则终止后续插件的处理 -
如果是对象可以包含
SourceMap
、AST
等
-
六、transform
紧接着 Rollup
执行所有的 transform
钩子来对模块内容进行进行自定义的转换,比如 babel
转译。transform
钩子也是非常常见的一个钩子函数,为Async + Sequential
类型,也就是异步串行钩子,作用是对加载后的模块内容进行自定义的转换。
6.1 语法
function Plugin(options){
return {
name: "rollup-plugin-xxx",
transform(code, id){
}
}
}
-
参数:
-
code
: 模块代码 -
id
: 模块ID
-
-
返回值: 返回一个包含
code
(代码内容) 和map
(SourceMap
内容) 属性的对象,当然也可以返回null
来跳过当前插件的transform
处理。需要注意的是,当前插件返回的代码会作为下一个插件transform
钩子的第一个入参,实现类似于瀑布流的处理。
七、moduleParsed
Rollup
拿到最后的模块内容,进行 AST
分析,得到所有的 import
内容,调用 moduleParsed
钩子:
-
如果是普通的
import
,则执行resolveId
钩子,继续回到步骤3。 -
如果是动态
import
,则执行resolveDynamicImport
钩子解析路径,如果解析成功,则回到步骤4加载模块,否则回到步骤3通过resolveId
解析路径。
八、resolveDynamicImport
Rollup
拿到最后的模块内容,进行 AST
分析,得到所有的 import
内容,调用 moduleParsed
钩子:
-
如果是普通的
import
,则执行resolveId
钩子,继续回到步骤3。 -
如果是动态
import
,则执行resolveDynamicImport
钩子解析路径,如果解析成功,则回到步骤4加载模块,否则回到步骤3通过resolveId
解析路径。
九、buildEnd
直到所有的 import
都解析完毕,Rollup
执行 buildEnd
钩子,Build
阶段结束。
十、outputOptions
执行所有插件的 outputOptions
钩子函数,对 output
配置进行转换。
十一、renderStart
执行 renderStart
,并发执行 renderStart
钩子,正式开始打包。
十二、banner、footer、intro、outro
并发执行所有插件的 banner
、footer
、intro
、outro
钩子(底层用 Promise.all
包裹所有的这四种钩子函数),这四个钩子功能很简单,就是往打包产物的固定位置(比如头部和尾部)插入一些自定义的内容,比如协议声明内容、项目介绍等等。
十三、renderDynamicImport
从入口模块开始扫描,针对动态 import
语句执行 renderDynamicImport
钩子,来自定义动态 import
的内容。
十四、augmentChunkHash
对每个即将生成的 chunk
,执行 augmentChunkHash
钩子,来决定是否更改 chunk
的哈希值,在 watch
模式下即可能会多次打包的场景下,这个钩子会比较适用。
十五、resolveFileUrl、resolveImportMeta
如果没有遇到 import.meta
语句,则进入下一步,否则:
-
对于
import.meta.url
语句调用resolveFileUrl
来自定义url
解析逻辑 -
对于其他
import.meta
属性,则调用resolveImportMeta
来进行自定义的解析。
十六、renderChunk
接着 Rollup
会生成所有 chunk
的内容,针对每个 chunk
会依次调用插件的 renderChunk
方法进行自定义操作,也就是说,在这里时候你可以直接操作打包产物了。
16.1 语法
function Plugin(options){
return {
name: "rollup-plugin-xxx",
renderChunk(code, chunk){
}
}
}
-
参数:
-
code
:chunk
代码内容 -
chunk
:chunk
元信息
-
-
返回值: 返回值跟
transform
钩子类似,既可以返回包含code
和map
属性的对象,也可以通过返回null
来跳过当前钩子的处理。
十七、generateBundle
随后会调用 generateBundle
钩子,这个钩子的入参里面会包含所有的打包产物信息,包括 chunk
(打包后的代码)、asset
(最终的静态资源文件)。你可以在这里删除一些 chunk
或者 asset
,最终这些内容将不会作为产物输出。**generateBundle
**也是异步串行的钩子,你可以在这个钩子里面自定义删除一些无用的 chunk 或者静态资源,或者自己添加一些文件。
17.1 语法
function Plugin(options){
return {
name: "rollup-plugin-xxx",
async generateBundle(output, bundle){
}
}
}
-
参数:
-
output
:output
配置 -
bundle
: 所有打包产物的元信息对象
-
十八、writeBundle
前面提到了 rollup.rollup
方法会返回一个bundle
对象,这个对象是包含generate
和write
两个方法,两个方法唯一的区别在于后者会将代码写入到磁盘中,同时会触发writeBundle
钩子,传入所有的打包产物信息,包括 chunk
和 asset
,和 generateBundle
钩子非常相似。
不过值得注意的是,这个钩子执行的时候,产物已经输出了,而 generateBundle
执行的时候产物还并没有输出。顺序如下图所示:
十九、closeBundle
当上述的 bundle
的 close
方法被调用时,会触发 closeBundle
钩子,到这里 Output
阶段正式结束。
二十、renderError
注意: 当打包过程中任何阶段出现错误,会触发 renderError
钩子,然后执行 closeBundle
钩子结束打包。