前言
Rollup 的强大在于其精简的插件系统。一个 Rollup 插件本质上就是一个包含各种"钩子函数"的对象。理解这些钩子的执行时序,是编写高性能插件、优化构建流程的关键。本文将带你深度复盘 Rollup 的两大核心阶段:构建 (Build) 与 输出 (Output) 。
一、构建阶段钩子函数(核心阶段)
构建阶段主要负责模块的解析、加载和转换,最终完成模块依赖图的构建,是Rollup打包的基础。该阶段可细分为5个小阶段,钩子执行顺序固定为:
初始化阶段(options、buildStart)→ 模块加载阶段(resolveId、load)→ 模块转换阶段(transform、moduleParsed)→ 代码生成阶段(augmentChunkHash、resolveDynamicImport)→ 代码构建阶段(buildEnd)
1. 初始化阶段钩子(options、buildStart)
options
-
执行时机:在读取用户配置之后、构建开始之前执行。
-
作用:可以添加或修改默认配置项(如调整input、output、plugins等Rollup核心配置)。
-
注意:仅支持同步执行,无法进行异步操作;此钩子修改的配置会覆盖用户默认配置,需谨慎使用。
buildStart
-
执行时机:开始解析模块前执行(构建流程启动的第一个核心钩子)。
-
作用:用于初始化插件状态(如重置计数器、初始化缓存)、读取外部文件(如配置文件、静态资源清单)等。
-
支持:同步、异步执行(可返回Promise);此钩子可访问传递给
rollup.rollup()的最终配置,包含所有options钩子的转换结果和默认值。
2. 模块加载阶段钩子(resolveId、load)
resolveId(source, importer)
-
执行时机:它是在Rollup遇到一个 import 语句时(如
import foo from './foo.js')执行,是模块解析的核心钩子。 -
作用:它可以将模块标识符(如
'./foo.js'或'vue')解析为绝对路径或模块 ID,返回一个解析后的路径ID(返回值可以是 null、string 或者一个对象,如果返回false则视为外部模块,不打包)。支持同步、异步执行。 -
入参说明:
source:表示 import 的内容(字符串),即模块标识符;importer:表示导入该模块的文件路径(绝对路径),入口文件的importer为 null。
如果多个插件都定义了resolveId,会按插件配置顺序执行,直到某个插件返回非null/undefined的值(表示解析完成);也可通过配置
order: 'pre'调整钩子执行优先级,实现优先解析特定模块。
示例:拦截虚拟模块导入,自定义模块解析逻辑:
javascript
resolveId(source) {
if (source === 'virtual-module') {
// 表示rollup不应询问其他插件或从文件系统检查此ID
return source;
}
return null; // 其他ID按正常逻辑处理
}
load(id)
-
执行时机:它在 resolveId 返回一个 ID 后执行,是模块加载的核心钩子。
-
作用:用于获取对应模块的源码,并返回这个源码给transform钩子进行后续转换。
-
支持:同步、异步执行;若返回null,Rollup会默认从文件系统读取该ID对应的文件内容,也可通过
this.load在其他钩子中触发模块预加载。
示例:自定义虚拟模块的源码加载:
javascript
load(id) {
if (id === 'virtual-module') {
// 返回虚拟模块的源码
return 'export default "This is virtual!"';
}
return null; // 其他ID按正常逻辑加载
}
3. 模块转换阶段钩子(transform、moduleParsed)
transform(code, id)
-
执行时机:它在模块源码加载后执行,紧随load钩子之后。
-
作用:它用于将模块源码中的ts、tsx等非标准JS语法转换为标准的js语法,也可对源码进行压缩、注入代码等自定义处理。支持同步、异步执行
-
入参说明:
code:模块的源码字符串(load钩子返回的内容);id:模块 ID(通常是文件路径,与resolveId返回的ID一致)。
-
返回值:
{ code: '修改后的代码', map: 'sourcemap' },其中sourcemap可选,用于关联转换后的代码与原始源码,方便调试。
moduleParsed
-
执行时机:在模块被 Rollup 解析为 AST(抽象语法树)后执行。
-
作用:可以用于分析模块信息(如导入导出关系)、收集元数据(如模块依赖、变量声明),实际开发中较少使用。
-
支持:同步、异步执行;入参为
moduleInfo,包含当前模块的详细信息,执行完成后会并行解析模块中所有静态和动态导入的依赖。
4. 代码生成阶段钩子(augmentChunkHash、resolveDynamicImport)
augmentChunkHash
-
执行时机:在生成 chunk 哈希前执行(chunk 哈希用于实现静态资源长效缓存)。
-
作用:可以向 chunk 哈希添加额外信息(如插件版本、配置参数),确保当这些信息变化时,chunk 哈希也会更新,避免缓存失效不及时。
resolveDynamicImport
执行时机:当遇到动态导入语句时(如import('./foo.js'))执行。
作用:处理动态导入的解析,作用和resolveId类似,但专用于动态导入场景,可自定义动态导入的模块解析规则。
5. 代码构建阶段钩子(buildEnd)
-
执行时机:构建结束(无论成功或失败)执行,是构建阶段的最后一个钩子。
-
作用:用于清理资源(如关闭文件流、清空缓存)、上报错误(如构建失败日志上报)等。
-
支持:同步、异步执行。
二、打包阶段钩子函数(产物输出阶段)
打包阶段主要负责将构建阶段处理后的模块,生成最终的可部署产物,并写入磁盘,钩子执行顺序固定为:
输出生成(renderStart→renderChunk→generateBundle)→ 输出写入(writeBundle→closeBundle)
1. 输出生成阶段钩子(renderStart、renderChunk、generateBundle)
renderStart
-
执行时机:开始生成 chunk 内容前执行,是打包阶段的第一个钩子。
-
作用:初始化输出相关状态(如初始化产物计数器、设置输出格式相关参数)。
-
支持:同步、异步执行。
renderChunk(code, chunk, options)
-
执行时机:它是在每个 chunk 生成后、写入磁盘前执行。
-
作用:它可以对生成的chunk 中的JS 代码进行最后处理(例如注入版权注释、补充全局变量、代码压缩优化等)。支持同步、异步执行。
-
入参说明:
code:当前chunk生成后的JS代码字符串;chunk:当前chunk的详细信息(如chunk名称、包含的模块、依赖关系等);options:当前的输出配置(与output配置一致)。
generateBundle
-
执行时机:它是在所有 chunk 和 asset 生成完毕,即将写入磁盘前执行。
-
作用:这个钩子的入参里面会包含所有的打包产物信息,包括 chunk (打包后的代码)、asset(最终的静态资源文件)。可以在这里检查、修改、添加最终输出文件(例如删除无用 chunk、合并CSS、注入 preload 链接到 HTML)。
-
支持同步、异步执行;是打包阶段最常用的钩子之一,可用于最终产物的自定义优化。
2. 输出写入阶段钩子(writeBundle、closeBundle)
writeBundle
-
执行时机:它是在bundle 已写入磁盘后执行,仅在调用
bundle.generate()和bundle.write()时触发。 -
作用:可以在这执行写入后的操作(如将产物上传 CDN、生成产物清单、通知部署服务等)。
-
支持:同步、异步执行。
closeBundle
-
执行时机:它是在整个构建完全结束执行,是Rollup打包流程的最后一个钩子。
-
作用:可以在这做一些全局清理操作(如关闭数据库连接、清空临时文件、终止子进程等)。
-
支持:同步、异步执行;无论构建成功或失败,都会执行此钩子。
三、钩子执行顺序(核心重点)
scss
// 完整执行顺序
options → buildStart → resolveId → load → transform → moduleParsed →
augmentChunkHash → resolveDynamicImport → buildEnd → renderStart →
renderChunk → generateBundle → writeBundle → closeBundle
注意:所有钩子均支持同步执行,标注"支持异步"的钩子可返回Promise,实现异步操作(如读取外部文件、请求接口);多个插件定义同一钩子时,按插件配置顺序执行。
四、补充
-
钩子使用场景:开发Rollup插件时,可根据需求选择对应阶段的钩子(如语法转换用transform、产物优化用generateBundle、CDN上传用writeBundle);
-
与Vite关联:Vite生产环境基于Rollup打包,Vite插件可直接使用Rollup的所有钩子,同时Vite会自动注入内置插件,无需手动配置基础钩子(如resolveId、load);
-
调试技巧:可在钩子中打印日志(如
console.log('钩子执行:', id)),查看钩子执行顺序和入参信息,快速排查插件问题。