普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月18日首页

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

2026年4月17日 23:41

前言

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'调整钩子执行优先级,实现优先解析特定模块。

示例:拦截虚拟模块导入,自定义模块解析逻辑:

resolveId(source) {
  if (source === 'virtual-module') {
    // 表示rollup不应询问其他插件或从文件系统检查此ID
    return source;
  }
  return null; // 其他ID按正常逻辑处理
}

load(id)

  • 执行时机:它在 resolveId 返回一个 ID 后执行,是模块加载的核心钩子。

  • 作用:用于获取对应模块的源码,并返回这个源码给transform钩子进行后续转换。

  • 支持:同步、异步执行;若返回null,Rollup会默认从文件系统读取该ID对应的文件内容,也可通过this.load在其他钩子中触发模块预加载。

示例:自定义虚拟模块的源码加载:

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打包流程的最后一个钩子。

  • 作用:可以在这做一些全局清理操作(如关闭数据库连接、清空临时文件、终止子进程等)。

  • 支持:同步、异步执行;无论构建成功或失败,都会执行此钩子。

三、钩子执行顺序(核心重点)

// 完整执行顺序
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)),查看钩子执行顺序和入参信息,快速排查插件问题。

深度解析 Rollup 配置与 Vite 生产构建流程

2026年4月17日 23:02

前言

为什么 Vite 在生产环境不使用 ESBuild 而是选择 Rollup?为什么 Rollup 打包出来的代码比 Webpack 更纯粹?本文将带你深入 Rollup 的核心配置,并拆解 Vite 是如何驱动 Rollup 完成生产环境构建的。

一、 Rollup 核心配置:构建系统的“方向盘”

1. 核心概念

Rollup 是 Vite 生产环境下的底层打包工具,专注于 ES 模块的打包优化。

注意:在 Vite 项目中,不需要单独编写rollup.config.js文件,所有 Rollup 相关的配置都统一写在vite.config.js/tsbuild.rollupOptions字段中。Vite 会自动将你的配置与内置的 Rollup 配置合并,生成最终的打包配置。

2. 核心特点

  • 具有天然的 Tree Shaking 功能,可以静态分析 ES 模块的导入导出关系,精准移除未使用到的代码
  • 支持Scope Hoisting(作用域提升) ,将多个模块的代码合并到同一个作用域中,减少函数包裹和运行时开销
  • 打包产物体积小、执行效率高,特别适合用于 JavaScript 库和工具的打包
  • 插件系统简洁强大,易于扩展和定制

二、Rollup 核心配置项详解

1. input(打包入口)

用于指定打包的入口文件,支持三种写法:

  • 字符串:单入口,所有代码打包到一个文件中,适合 SPA 单页面应用
  • 数组:多入口,每个入口生成独立的 chunk 文件,公共依赖会自动拆分
  • 对象:多入口,可自定义每个 chunk 的名称,是最灵活的写法
// 单入口
input: 'src/index.js'

// 数组多入口
input: ['src/page1.js', 'src/page2.js']

// 对象多入口(推荐)
input: {
  home: 'src/pages/home.js',
  about: 'src/pages/about.js',
  vendor: 'src/utils/vendor.js'
}

原内容保留:如果是多文件的话,会给每个入口生成独立文件,公共依赖会在打包过程中拆分出来,适用于 MPA(多页应用)、库的多版本。

2. output(打包输出)

该属性为对象或对象数组类型,可以指定打包后的文件输出规则。如果配置为数组,可以将同一份代码打包成多种不同格式的包同时输出。output 核心属性如下:

属性名 类型 说明
dir string 输出目录,当有多个 chunk 时必须使用此属性
file string 单个输出文件的路径,仅适用于单入口单 chunk 的情况
format string 输出格式,支持:- esm:ES Module 格式(Vite 默认)- cjs:CommonJS 格式- umd:通用模块定义- iife:立即执行函数格式
name string 打包为iifeumd格式时必须配置,指定对外暴露的全局变量名
globals object 全局变量声明,用于将外部依赖映射为全局变量示例:{ jquery: '$' }表示项目中可以直接用$代替jquery
sourcemap boolean 是否生成源码映射文件,方便生产环境调试
assetFileNames string 静态资源文件输出文件名模板示例:'assets/[name]-[hash][extname]'

下面的例子代表第一次打包(ESM 格式),将结果输出到dist/es/目录,第二次打包(CJS 格式),将结果输出到dist/cjs/目录

output: [
  {
    dir: "dist/es",    // 输出到 dist/es 目录
    format: "esm",     // ES Module 格式
  },
  {
    dir: "dist/cjs",   // 输出到 dist/cjs 目录
    format: "cjs",     // CommonJS 格式
  },
]

3. external(外部依赖)

用于标记某些模块为外部依赖,告诉 Rollup 这些模块不应该被打包到最终的 bundle 中。对于某些第三方包,有时候我们不想让 Rollup 进行打包,也可以通过 external 进行外部化。external 支持三种写法:

// 字符串写法
external: ['vue', 'react']

// 正则写法
external: [/^lodash/]

// 函数写法(最灵活)
external: (id) => {
  // 所有node_modules中的模块都标记为外部依赖
  return id.includes('node_modules')
}

使用场景

  • 库打包时,将核心依赖(如 Vue、React)外部化,避免重复打包
  • 减少打包体积,提高构建速度
  • 利用 CDN 加载公共依赖

4. plugins(插件系统)

用于扩展和定制 Rollup 的构建流程。该配置项可以与output配置在同一级(全局生效),也可以配置在 output 参数里面(仅对该输出生效)。可使用自定义编写的 Rollup 插件,也可以使用第三方插件。

常用 Rollup 插件整理如下:

插件名称 作用
@rollup/plugin-json 支持.json文件的加载,并配合 Tree Shaking 去掉未使用的部分
@rollup/plugin-babel 使用 Babel 进行 JS 代码的语法转译,兼容低版本浏览器
@rollup/plugin-typescript 支持使用 TypeScript 开发
@rollup/plugin-alias 支持路径别名配置
@rollup/plugin-replace 在打包过程中进行变量字符串的替换
@rollup/plugin-node-resolve 解析 node_modules 中的第三方依赖
@rollup/plugin-commonjs 将 CommonJS 模块转换为 ES 模块,供 Rollup 处理
rollup-plugin-visualizer 对打包产物进行分析,自动生成产物体积可视化分析图

补充说明@rollup/plugin-node-resolve@rollup/plugin-commonjs是 Rollup 处理第三方依赖的必备插件,Vite 已经内置了这两个插件,不需要手动配置。

这篇笔记非常深入地探讨了 Rollup 在 Vite 体系中的地位。作为现代前端打包工具的“幕后英雄”,Rollup 的配置和流程是进阶高级前端的必修课。

为了适配掘金的风格,我为你优化了标题,并引入了“声明式配置图解”和“双阶段构建流”的概念,同时修正了部分关于 input 拼写的小细节。


进阶必备:深度解析 Rollup 配置与 Vite 生产构建流程

前言

为什么 Vite 在生产环境不使用 ESBuild 而是选择 Rollup?为什么 Rollup 打包出来的代码比 Webpack 更纯粹?本文将带你深入 Rollup 的核心配置,并拆解 Vite 是如何驱动 Rollup 完成生产环境构建的。


一、 Rollup 核心配置:构建系统的“方向盘”

Rollup 的配置以简洁著称,其设计的核心目标是打包出最干净的代码库

1. 入口与出口 (Input & Output)

  • input (注意是单数):支持字符串或数组/对象。

    • 单入口:适合单页面应用 (SPA),生成一个主 bundle。
    • 多入口:适合多页面应用 (MPA) 或组件库,Rollup 会自动提取公共依赖。
  • output:支持数组形式,实现一份源码,多种格式输出

    • format:

      • esm: 现代浏览器首选。
      • cjs: Node.js 环境使用。
      • umd: 兼容 AMD/CommonJS/全局变量。
    • globals: 映射外部依赖,如 { jquery: '$' }

2. 外部依赖 (External)

核心作用:标记某些模块不被打包。

  • 场景:在开发组件库时,通常会将 vuereact 设为 external,让宿主环境提供这些依赖,减小打包体积。

3. 常用插件全家桶

插件名称 核心作用
@rollup/plugin-json 让 JS 能直接 import json,并支持 Tree Shaking。
@rollup/plugin-babel 配合 Babel 进行语法降级,解决兼容性问题。
@rollup/plugin-typescript 让 Rollup 具备处理 TS 的能力。
@rollup/plugin-alias 配置路径别名(如 @ 指向 src)。
rollup-plugin-visualizer 神器:生成体积分析图,优化首屏加载必看。

三、 Vite 视角下的 Rollup 构建流程

执行vite build命令后,Vite 会先完成自身的预处理工作,然后将所有打包任务委托给 Rollup 执行。整个流程可以分为以下三个核心阶段:

阶段 1:Vite 配置预处理与 Rollup 配置生成

Vite 读取vite.config.js,先分离 Vite 非 Rollup 相关的配置和 Rollup 相关配置;将 Vite 内置的处理 Vue/TS/CSS/ 静态资源插件注入,再合并用户配置的插件,最终生成标准的包含inputoutputplugins等核心字段的Rollup 配置对象

接着 Vite 调用 Rollup 的rollup.rollup()方法,传入上述配置,启动 Rollup 构建流程。

阶段 2:Rollup 构建阶段(建立模块依赖图)

Rollup 从input指定的入口文件开始,依次执行所有插件的resolveId(解析模块路径)→load(加载文件内容)→transform(转换文件为标准 ES 模块)钩子来处理各类资源。在处理的过程中会递归解析所有导入的模块,直到所有依赖解析完成,建立完整的模块依赖图。

补充:这个阶段只进行模块的解析和转换,不会生成任何输出文件。所有的文件内容都会被加载到内存中,形成一个完整的模块树。

阶段 3:Rollup 生成阶段(产物生成与输出)

在构建打包的过程中,Vite 会基于模块依赖图执行 Tree-shaking 移除未使用代码、Scope Hoisting 作用域提升,并根据output配置进行代码分割将代码拆分成多个独立的 chunks 文件,实现按需加载。Vite 插件在此阶段补充处理(将.vue、.ts 等文件编译为标准的 js 文件、并处理 css 将其压缩成单独的 css 文件等)。

最后调用bundle.generate()在内存中生成编译后的 JS/CSS/HTML 等产物,和bundle.write()将内存中的产物写入磁盘(默认dist目录),最终生成可部署的静态资源文件。

补充bundle.generate()只在内存中生成产物,不写入磁盘;bundle.write()会先调用bundle.generate(),然后将产物写入到指定的输出目录。

总结

Rollup 作为 Vite 生产环境的底层打包工具,是理解 Vite 打包原理的关键。掌握 Rollup 的核心配置和构建流程,不仅能帮助你解决生产环境中的各种打包问题,还能让你更灵活地定制 Vite 的构建流程,实现更高效的打包优化和插件开发。

❌
❌