Rollup 核心插件系统
Rollup 本身只负责核心的打包逻辑,诸如模块解析、语法转换、代码压缩、开发服务器等高级能力,几乎全部通过插件完成
- 插件是一个符合特定接口规范的普通 JavaScript 对象
- 插件通过实现不同的 钩子(Hook)函数 介入 Rollup 的完整生命周期,包括:
- 读取配置与初始化
- 解析模块依赖
- 加载与转换源码
- 生成产物与写入磁盘
- 大部分“工程化能力”都是通过组合多个插件完成的,例如:
- 支持 TypeScript、JSX、Vue 单文件组件
- Tree-shaking 友好的 Babel 转译
- 处理 JSON、CSS、图片等非 JS 资源
- 生产环境压缩、混淆与分析
插件基础 API
插件对象结构
一个最小可用的插件通常是一个“工厂函数”,返回包含若干钩子的对象:
export default function myPlugin(options = {}) {
return {
name: "my-plugin",
buildStart(inputOptions) {
console.log("build start", inputOptions.input)
},
generateBundle(outputOptions, bundle, isWrite) {
console.log("generate bundle", Object.keys(bundle), isWrite)
}
}
}常见字段:
name: 插件名称,必填,用于错误栈和调试输出- 各种钩子函数:如
options、buildStart、resolveId、load、transform、generateBundle等
使用时,只需要在 rollup.config 中把插件加入 plugins 数组:
import myPlugin from "./my-rollup-plugin.js"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "es",
sourcemap: true
},
plugins: [myPlugin()]
}常用构建阶段钩子
下表列出构建阶段最常用的钩子及其用途(按典型调用顺序排列):
| 钩子名 | 调用时机 | 函数签名(简化) | 返回值 | 常见用途 |
|---|---|---|---|---|
options | 处理用户输入配置之前 | (inputOptions) | 新的或修改后的 inputOptions | 动态修改或补全配置 |
buildStart | 构建开始 | (inputOptions) | void | 初始化插件状态、打印信息 |
resolveId | 解析模块 ID 时 | (source, importer, options) | 字符串或对象,或 null | 自定义模块解析、别名、虚拟模块 |
load | 加载模块内容时 | (id) | 源码字符串或对象,或 null | 读取虚拟模块内容或特殊资源 |
transform | 每个模块加载完成后 | (code, id) | 新代码字符串或 { code, map } | 代码转换、宏展开、注入辅助函数 |
moduleParsed | AST 解析完成后 | (moduleInfo) | void | 基于 AST 的分析或收集信息 |
buildEnd | 构建完成(无论成功与否) | (error) | void | 释放资源、输出日志 |
watchChange | watch 模式下文件变更时 | (id, change) | void | 响应文件变更,更新内部缓存 |
常用输出阶段钩子
输出阶段主要关注生成 bundle、重写代码片段和落盘逻辑:
| 钩子名 | 调用时机 | 函数签名(简化) | 返回值 | 常见用途 |
|---|---|---|---|---|
outputOptions | 处理每个输出配置之前 | (outputOptions) | 新的或修改后的 outputOptions | 动态修改输出目录、文件名、格式 |
renderStart | 开始渲染某次输出时 | (outputOptions, inputOptions) | void | 输出信息、准备辅助数据 |
renderChunk | 生成单个 chunk 代码时 | (code, chunk, options) | 新代码字符串或 { code, map } | 注入运行时代码、按 chunk 做优化 |
augmentChunkHash | 计算 chunk hash 时 | (chunkInfo) | 字符串 | 控制缓存策略、强制 hash 变更 |
generateBundle | 输出 bundle 元信息阶段 | (outputOptions, bundle, isWrite) | void 或修改 bundle | 生成额外文件、写出清单或分析报告 |
writeBundle | 所有文件写入磁盘后 | (outputOptions, bundle) | void | 输出日志、上传产物、清理临时文件 |
closeBundle | 整个构建流程完全结束后 | () | void | 关闭连接、释放全局资源 |
插件上下文(this)常用方法
在钩子内部,this 指向插件上下文,可以调用一些工具方法:
this.warn(message):输出非致命警告this.error(message):抛出构建错误并终止流程this.emitFile(descriptor):声明资源文件或 chunk,例如{ type: "asset", name, source }this.getFileName(fileId):通过emitFile得到的fileId获取最终产物文件名this.addWatchFile(id):在 watch 模式下额外监听某个文件this.getModuleInfo(id):获取模块依赖信息、导入导出列表等
这些 API 是实现复杂自定义插件(例如多入口控制、分析工具、资源管理)的基础
示例:在插件中使用 this.error / this.warn
在自定义插件里,推荐使用上下文方法进行错误与警告输出,例如在构建开始阶段检查必要的环境变量:
export default function checkEnvPlugin() {
return {
name: "check-env",
buildStart() {
if (!process.env.NODE_ENV) {
this.warn("NODE_ENV is not set, defaulting to development")
}
}
}
}插件配置方式与执行顺序
基本配置方式
在 rollup.config.js 中,通过 plugins 数组配置插件:
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import json from "@rollup/plugin-json"
import { babel } from "@rollup/plugin-babel"
import typescript from "@rollup/plugin-typescript"
import { terser } from "@rollup/plugin-terser"
export default {
input: "src/index.ts",
output: [
{
file: "dist/index.cjs",
format: "cjs",
sourcemap: true
},
{
file: "dist/index.esm.js",
format: "es",
sourcemap: true
}
],
plugins: [
nodeResolve({
extensions: [".mjs", ".js", ".json", ".ts"]
}),
commonjs(),
json(),
typescript({
tsconfig: "./tsconfig.json"
}),
babel({
babelHelpers: "bundled",
extensions: [".js", ".ts"],
exclude: "node_modules/**"
}),
terser()
]
}此配置是一个完整可运行的示例,适合构建一个 TypeScript 编写的通用 JS 库
插件执行顺序规则
- 构建阶段钩子(如
resolveId、load、transform)按plugins数组顺序依次执行 - 输出阶段钩子(如
renderChunk、generateBundle、writeBundle)则按相反顺序执行 - 某些钩子只在特定阶段被调用,例如:
watchChange只在--watch模式下触发closeBundle在所有输出结束后触发
因此通常的插件排列顺序建议是:
- 模块解析类插件:
@rollup/plugin-node-resolve、别名插件等 - 兼容性处理插件:
@rollup/plugin-commonjs - 语法转换插件:
@rollup/plugin-babel、@rollup/plugin-typescript - 资源处理插件:CSS、图片、JSON、Vue 等
- 产物优化插件:
@rollup/plugin-terser、分析可视化插件等
按环境启用不同插件
实际项目中通常会根据环境选择不同插件,例如开发环境需要 HMR,生产环境需要压缩:
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import { terser } from "@rollup/plugin-terser"
const isProd = process.env.NODE_ENV === "production"
export default {
input: "src/main.js",
output: {
file: isProd ? "dist/bundle.min.js" : "dist/bundle.js",
format: "iife",
sourcemap: !isProd
},
plugins: [nodeResolve(), commonjs(), isProd && terser()].filter(Boolean)
}常用官方插件详解
本节对常用官方插件的 功能、常用配置项和使用场景 进行说明
@rollup/plugin-node-resolve
@rollup/plugin-node-resolve 支持从 node_modules 中解析第三方依赖,识别 module、main 等字段
npm install --save-dev @rollup/plugin-node-resolve常用配置项:
extensions: 解析时尝试的文件后缀数组,默认[".mjs", ".js", ".json", ".node"]。browser: 为浏览器环境选择package.json中的browser字段moduleDirectories: 额外的模块查找目录。
示例:
import { nodeResolve } from "@rollup/plugin-node-resolve"
export default {
input: "src/index.js",
output: {
file: "dist/bundle.js",
format: "es"
},
plugins: [
nodeResolve({
browser: true,
extensions: [".js", ".jsx", ".mjs"]
})
]
}典型场景:打包浏览器端项目或希望消费第三方库的 ESM 构建产物
@rollup/plugin-alias
@rollup/plugin-alias 用于为模块导入配置简洁的路径别名,避免层层 ../../../。
npm install --save-dev @rollup/plugin-alias常用配置项:
entries: 别名数组,形如{ find: '@', replacement: 'src' }。
示例:
// rollup.config.js
import alias from "@rollup/plugin-alias"
import { nodeResolve } from "@rollup/plugin-node-resolve"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "es",
sourcemap: true
},
plugins: [
alias({
entries: [
{ find: "@", replacement: "src" } // 支持 import('@/utils/xxx')
]
}),
nodeResolve()
]
}典型场景:统一管理项目内长路径导入,配合 TypeScript paths 或 Webpack alias 等保持一致
@rollup/plugin-commonjs
@rollup/plugin-commonjs 将 CommonJS 模块转换为 ES 模块,便于 Rollup 进行 Tree-shaking。
npm install --save-dev @rollup/plugin-commonjs常用配置项:
include: 指定需要转换的文件范围,支持通配符exclude: 指定不转换的文件ignoreTryCatch: 忽略某些包中的require调用
示例:
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "iife",
name: "MyApp"
},
plugins: [
nodeResolve(),
commonjs({
include: "node_modules/**"
})
]
}典型场景:依赖大量 CommonJS 包(例如老版本的工具库、Node 生态库)时
@rollup/plugin-replace
@rollup/plugin-replace 在构建时用指定字面量替换源码中的变量,常用于注入 process.env.NODE_ENV 或开关调试代码。
npm install --save-dev @rollup/plugin-replace使用时需要注意:替换值应为字符串字面量(通常通过 JSON.stringify 包装)。
示例:
// rollup.config.js
import replace from "@rollup/plugin-replace"
const isProd = process.env.NODE_ENV === "production"
export default {
input: "src/main.js",
output: {
file: isProd ? "dist/bundle.min.js" : "dist/bundle.js",
format: "iife",
name: "MyApp"
},
plugins: [
replace({
preventAssignment: true,
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || "development")
})
]
}典型场景:按环境注入开关、删除开发调试代码、控制日志输出等
@rollup/plugin-babel
@rollup/plugin-babel 集成 Babel,将现代 JavaScript 转换为兼容旧环境的代码
npm install --save-dev @rollup/plugin-babel @babel/core @babel/preset-env关键配置:
babelHelpers: 指定辅助函数的注入策略,常用值为"bundled"、"runtime"。extensions: 需要通过 Babel 处理的文件扩展名。exclude: 排除node_modules以提升性能。
示例:
import { babel } from "@rollup/plugin-babel"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "iife",
name: "MyApp"
},
plugins: [
babel({
babelHelpers: "bundled",
extensions: [".js", ".jsx"],
exclude: "node_modules/**"
})
]
}搭配 .babelrc:
{
"presets": [["@babel/preset-env", { "modules": false }]]
}注意:"modules": false 可以避免 Babel 把 ES 模块转换成 CommonJS,从而保持 Rollup 的 Tree-shaking 效果
@rollup/plugin-typescript
@rollup/plugin-typescript 直接编译 TypeScript 源码并交给 Rollup 继续处理
npm install --save-dev @rollup/plugin-typescript typescript常用配置项:
tsconfig: 指定tsconfig.json路径- 其他选项大多由
tsconfig.json控制,例如target、module
示例:
import typescript from "@rollup/plugin-typescript"
export default {
input: "src/index.ts",
output: {
dir: "dist",
format: "es"
},
plugins: [
typescript({
tsconfig: "./tsconfig.json"
})
]
}搭配 tsconfig.json:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "dist"
},
"include": ["src"]
}@rollup/plugin-json
@rollup/plugin-json 将 .json 文件视为模块导入,允许 import data from "./data.json"
npm install --save-dev @rollup/plugin-json常用配置项:
preferConst: 使用const声明导出变量compact: 是否压缩生成代码
示例:
import json from "@rollup/plugin-json"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "es"
},
plugins: [
json({
compact: true,
preferConst: true
})
]
}@rollup/plugin-terser
@rollup/plugin-terser 使用 Terser 对生成的代码进行压缩与混淆,多用于生产构建
npm install --save-dev @rollup/plugin-terser示例:
import { terser } from "@rollup/plugin-terser"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.min.js",
format: "iife",
name: "MyApp"
},
plugins: [
terser({
compress: {
drop_console: true
}
})
]
}社区插件与典型应用场景
除了官方插件,社区还提供了大量插件以支持 CSS、图片、Vue/React 组件等多种场景。推荐的插件列表可以参考 awesome-rollup
处理 CSS:rollup-plugin-postcss
典型能力:
- 支持导入 CSS、Less、Sass 等
- 支持自动添加厂商前缀、CSS Modules
- 可以将 CSS 抽离为独立文件
示例:
import postcss from "rollup-plugin-postcss"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "iife",
name: "MyApp"
},
plugins: [
postcss({
modules: true,
extract: "styles.css"
})
]
}打包 Vue 组件:rollup-plugin-vue
示例(库型 Vue 组件打包):
import vue from "rollup-plugin-vue"
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
export default {
input: "src/index.js",
output: {
file: "dist/my-vue-lib.esm.js",
format: "es",
sourcemap: true
},
external: ["vue"],
plugins: [nodeResolve(), commonjs(), vue()]
}打包 React 组件库
通常使用官方插件组合即可:
import { nodeResolve } from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs"
import { babel } from "@rollup/plugin-babel"
export default {
input: "src/index.jsx",
output: {
file: "dist/my-react-lib.esm.js",
format: "es",
sourcemap: true
},
external: ["react", "react-dom"],
plugins: [
nodeResolve({
extensions: [".js", ".jsx"]
}),
commonjs(),
babel({
babelHelpers: "bundled",
extensions: [".js", ".jsx"],
exclude: "node_modules/**"
})
]
}使用社区插件的最佳实践
- 检查维护状态:优先选择活跃维护、下载量高、文档完善的插件
- 关注插件顺序:解析类插件在前,转换类插件居中,压缩类插件在最后
- 阅读 README:很多插件有非直观的选项,必须查看官方文档
- 避免功能重叠:同类插件只选择一个,避免在
transform阶段做重复工作
自定义插件实践
当现有插件无法满足需求时,可以编写自定义插件。下面通过一个简单示例展示完整开发流程
示例:替换 process.env.* 环境变量
插件实现:
export default function envReplacePlugin(env = {}) {
const replacements = Object.entries(env).map(([key, value]) => ({
key: `process.env.${key}`,
value: JSON.stringify(value)
}))
return {
name: "env-replace",
transform(code) {
let transformed = code
for (const item of replacements) {
transformed = transformed.split(item.key).join(item.value)
}
return {
code: transformed,
map: null
}
}
}
}在配置中使用:
import envReplacePlugin from "./my-env-replace-plugin.js"
export default {
input: "src/main.js",
output: {
file: "dist/bundle.js",
format: "es"
},
plugins: [
envReplacePlugin({
NODE_ENV: "production",
API_BASE_URL: "https://api.example.com"
})
]
}示例:构建日志与产物统计
示例,在构建前后打印信息和统计产物:
export default function buildReporter() {
let startTime = 0
return {
name: "build-reporter",
buildStart() {
startTime = Date.now()
console.log("Rollup build started")
},
generateBundle(outputOptions, bundle) {
const files = Object.keys(bundle)
console.log("Generated files:", files.length)
for (const file of files) {
const asset = bundle[file]
const size = Buffer.byteLength(asset.code || asset.source, "utf8")
console.log(file, size, "bytes")
}
},
closeBundle() {
const duration = Date.now() - startTime
console.log("Rollup build finished in", duration, "ms")
}
}
}该插件利用了 buildStart、generateBundle 和 closeBundle 三个钩子,覆盖了从开始构建到输出结束的完整生命周期