深度解密 Rollup 插件开发:核心钩子函数全生命周期图鉴

前言

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,实现异步操作(如读取外部文件、请求接口);多个插件定义同一钩子时,按插件配置顺序执行。

四、补充

  1. 钩子使用场景:开发Rollup插件时,可根据需求选择对应阶段的钩子(如语法转换用transform、产物优化用generateBundle、CDN上传用writeBundle);

  2. 与Vite关联:Vite生产环境基于Rollup打包,Vite插件可直接使用Rollup的所有钩子,同时Vite会自动注入内置插件,无需手动配置基础钩子(如resolveId、load);

  3. 调试技巧:可在钩子中打印日志(如console.log('钩子执行:', id)),查看钩子执行顺序和入参信息,快速排查插件问题。

相关推荐
java_nn2 小时前
一文了解前端技术
前端
发现一只大呆瓜2 小时前
深度解析 Rollup 配置与 Vite 生产构建流程
前端·vite
小码哥_常3 小时前
安卓黑科技:让手机成为你的“跌倒保镖”
前端
小李子呢02113 小时前
前端八股Vue---Vue2和Vue3的区别,set up的用法
前端·javascript·vue.js
m0_647057963 小时前
Harness Engineering 实践指南
前端
JJay.4 小时前
Android BLE 稳定连接的关键,不是扫描,而是 GATT 操作队列
android·服务器·前端
星空椰4 小时前
JavaScript 进阶基础:函数、作用域与常用技巧总结
开发语言·前端·javascript
奔跑的呱呱牛4 小时前
@giszhc/vue-page-motion:Vue3 路由动画怎么做才“丝滑”?(附在线示例)
前端·javascript·vue.js
ThridTianFuStreet小貂蝉4 小时前
面试题4:讲一讲HTML5、CSS3新特性
前端·css3·html5