跳到主要内容

钩子

2023年12月19日
柏拉文
越努力,越幸运

一、认识


1.1 构建阶段

插件的各种 Hook 可以根据这两个构建阶段分为两类: Build HookOutput Hook

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

  • Output Hook: (官方称为Output Generation Hook),则主要进行代码的打包,对于代码而言,操作粒度一般为 chunk级别(一个 chunk 通常指很多文件打包到一起的产物)。

1.2 执行方式

除了根据构建阶段可以将 Rollup 插件进行分类,根据不同的 Hook 执行方式也会有不同的分类,主要包括AsyncSyncParallelSequentialFirst这五种。在后文中我们将接触各种各样的插件 Hook,但无论哪个 Hook 都离不开这五种执行方式。

  • Async & Sync: 首先是 AsyncSync 钩子函数,两者其实是相对的,分别代表异步和同步的钩子函数,两者最大的区别在于同步钩子里面不能有异步逻辑,而异步钩子可以有。

  • Parallel: 这里指并行的钩子函数。如果有多个插件实现了这个钩子的逻辑,一旦有钩子函数是异步逻辑,则并发执行钩子函数,不会等待当前钩子完成(底层使用 Promise.all)。比如对于Build阶段的buildStart钩子,它的执行时机其实是在构建刚开始的时候,各个插件可以在这个钩子当中做一些状态的初始化操作,但其实插件之间的操作并不是相互依赖的,也就是可以并发执行,从而提升构建性能。反之,对于需要依赖其他插件处理结果的情况就不适合用 Parallel 钩子了,比如 transform

  • Sequential: 指串行的钩子函数。这种 Hook 往往适用于插件间处理结果相互依赖的情况,前一个插件 Hook 的返回值作为后续插件的入参,这种情况就需要等待前一个插件执行完 Hook,获得其执行结果,然后才能进行下一个插件相应 Hook 的调用,如transform

  • First: 如果有多个插件实现了这个 Hook,那么 Hook 将依次运行,直到返回一个非 null 或非 undefined 的值为止。比较典型的 HookresolveId,一旦有插件的 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: 解析参数

  • 返回值: 返回值可以是 nullstring 或者一个对象

    • 返回值为 null 时,会默认交给下一个插件的 resolveId 钩子处理。

    • 返回值为 string 时,则停止后续插件的处理。这里为了让替换后的路径能被其他插件处理,特意调用了 this.resolve 来交给其它插件处理,否则将不会进入到其它插件的处理。

    • 返回值为一个对象,也会停止后续插件的处理,不过这个对象就可以包含更多的信息了,包括解析后的路径、是否被 enternal、是否需要 tree-shaking 等等,不过大部分情况下返回一个 string 就够用了。

五、load


Rollup 通过调用 load 钩子加载模块内容。loadAsync + First 类型,即异步优先的钩子,和 resolveId 类似。它的作用是通过 resolveId 解析后的路径来加载模块内容。

5.1 语法

function Plugin(options){
return {
name: "rollup-plugin-xxx",
load(id){

}
}
}
  • 参数:

    • id:
  • 返回值: 返回值一般是 nullstring 或者一个对象

    • 如果返回值为 null,则交给下一个插件处理

    • 如果返回值为 string 或者对象,则终止后续插件的处理

    • 如果是对象可以包含 SourceMapAST

六、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


并发执行所有插件的 bannerfooterintrooutro 钩子(底层用 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 钩子类似,既可以返回包含 codemap 属性的对象,也可以通过返回 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对象,这个对象是包含generatewrite两个方法,两个方法唯一的区别在于后者会将代码写入到磁盘中,同时会触发writeBundle钩子,传入所有的打包产物信息,包括 chunkasset,和 generateBundle钩子非常相似。

不过值得注意的是,这个钩子执行的时候,产物已经输出了,而 generateBundle 执行的时候产物还并没有输出。顺序如下图所示:

Preview

十九、closeBundle


当上述的 bundleclose 方法被调用时,会触发 closeBundle 钩子,到这里 Output 阶段正式结束。

二十、renderError


注意: 当打包过程中任何阶段出现错误,会触发 renderError 钩子,然后执行 closeBundle 钩子结束打包。