Rollup 库打包:Tree-shaking 友好的模块打包器
什么是 Rollup
Rollup 是一款专门为 JavaScript 库和组件设计的模块打包器。与 Webpack 等通用打包工具不同,Rollup 的核心优势在于 Tree-shaking(摇树优化)——它能够分析模块依赖图,并自动移除未使用到的代码(dead code),最终生成极致精简的 bundle。这使得 Rollup 特别适合发布可被其他项目引用的库,因为它输出的代码干净、体积小,且对 ES 模块(ESM)生态极其友好。
为什么用 Rollup 打包库
- 原生 Tree-shaking 支持:基于静态的 ES 模块导入/导出语法,精确消除未引用代码。
- 输出格式灵活:支持多种输出格式(IIFE、CJS、AMD、UMD、ESM),同一份配置可生成多个版本。
- 生成可读性高的 bundle:打包结果接近手写代码,便于调试,不会引入大量运行时代码。
- 专为库优化:更适合构建可复用的模块,而不是包含大量资源的应用。
安装 Rollup
在项目中安装 Rollup 作为开发依赖:
npm install --save-dev rollup
或使用 yarn:
yarn add --dev rollup
安装后你就可以在命令行直接使用 npx rollup。
第一个 bundle
创建一个简单的库入口文件 src/main.js:
// src/main.js
import { add } from './math.js';
export function sumThree(a, b, c) {
return add(add(a, b), c);
}
export const version = '1.0.0';
提供依赖模块 src/math.js:
// src/math.js
export function add(x, y) {
return x + y;
}
export function subtract(x, y) {
return x - y;
}
在终端执行打包命令:
npx rollup src/main.js --file dist/bundle.js --format esm
打开 dist/bundle.js,你会看到 Rollup 已经将代码合并为一个文件,并且 subtract 函数被完全移除了,因为它从未被 main.js 使用。这就是最基础的 Tree-shaking 效果。
配置文件
在实际项目中,我们需要更复杂的构建配置,通过 rollup.config.mjs 文件管理。
// rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { terser } from 'rollup-plugin-terser';
export default {
input: 'src/main.js', // 入口文件
output: {
file: 'dist/bundle.js', // 输出文件
format: 'esm', // 输出格式:ES Module
sourcemap: true, // 生成 source map
},
plugins: [
resolve(), // 解析第三方模块(node_modules)
commonjs(), // 将 CommonJS 模块转换为 ESM,以便 Tree-shaking
terser(), // 压缩代码(可选)
],
};
运行打包时指定配置文件:
npx rollup -c rollup.config.mjs
关键插件推荐
| 插件 | 功能 | 安装命令 |
|---|---|---|
@rollup/plugin-node-resolve |
查找 node_modules 中的第三方模块 |
npm i -D @rollup/plugin-node-resolve |
@rollup/plugin-commonjs |
将 CommonJS 模块转为 ESM,使其可被 Tree-shaking | npm i -D @rollup/plugin-commonjs |
@rollup/plugin-babel |
使用 Babel 转译代码(如语法的降级) | npm i -D @rollup/plugin-babel @babel/core @babel/preset-env |
rollup-plugin-terser |
压缩输出代码 | npm i -D rollup-plugin-terser |
在配置文件的 plugins 数组中按顺序添加这些插件即可生效。
输出格式详解
Rollup 通过 output.format 控制生成的模块格式。对于库开发者,通常需要同时输出多种格式以满足不同消费场景。
ES Module(esm)
现代打包器和浏览器原生支持的模块格式。
优点:支持静态分析,Tree-shaking 发挥最佳作用。
用法:在 package.json 的 module 字段指向该文件,让使用者打包时能进一步消除无用代码。
output: {
file: 'dist/mylib.esm.js',
format: 'esm',
}
CommonJS(cjs)
Node.js 传统模块格式。
主要用于 require() 引入的场景。
在 package.json 的 main 字段指向该文件。
output: {
file: 'dist/mylib.cjs.js',
format: 'cjs',
exports: 'named', // 可选:支持具名导出
}
Universal Module Definition(umd)
兼容浏览器全局变量、AMD 和 CommonJS 的格式。
适合直接通过 <script> 标签加载,或在较旧的环境中运行。
output: {
file: 'dist/mylib.umd.js',
format: 'umd',
name: 'MyLib', // 全局变量名
}
推荐做法:一次构建输出所有三种格式,可使用 output 为数组:
output: [
{ file: 'dist/mylib.esm.js', format: 'esm' },
{ file: 'dist/mylib.cjs.js', format: 'cjs', exports: 'named' },
{ file: 'dist/mylib.umd.js', format: 'umd', name: 'MyLib' },
],
同时记得更新 package.json:
{
"main": "dist/mylib.cjs.js",
"module": "dist/mylib.esm.js",
"browser": "dist/mylib.umd.js"
}
Tree-shaking 如何工作
Rollup 依赖 ES 模块的静态结构 实现 Tree-shaking。ES 模块的 import 和 export 语句在编译阶段即可确定依赖关系,而 CommonJS 的 require() 是动态的,无法在构建时分析。
过程简述:
- 从入口模块开始,通过
import语句构建模块依赖图。 - 遍历依赖图,标记所有被实际引用的
export(包括间接引用)。 - 移除未被任何引用触及的
export,连同其代码一并删除。 - 将剩余代码合并为单个文件(或按指定分块),同时尽可能保留模块边界(bundling 但不破坏结构)。
注意事项:
- 避免
export default {}导出整个对象,这样会引入所有属性,破坏 Tree-shaking。应使用具名导出。 - 副作用处理:若模块内包含顶层副作用代码(如修改
window),即便未引用其导出,Rollup 也可能保留。可通过package.json的sideEffects字段告知哪些文件无副作用。 - 使用 CommonJS 依赖时,必须搭配
@rollup/plugin-commonjs将语法转换为 ES 模块,否则无法参与 Tree-shaking。
处理第三方依赖和代码分块
库开发时,通常无需将 React、Vue 等大型依赖打入 bundle 中,而是将其设为外部(external),由使用者自行提供。
export default {
input: 'src/index.js',
external: ['react', 'react-dom'],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
// 仅 iife/umd 格式需要 globals,其他格式自动处理
},
};
如果需要代码拆分(code splitting),Rollup 也支持动态导入,只需将 output.format 设为 esm 或 system,并使用 output.dir 代替 output.file,Rollup 会自动生成多个 chunk。
常见问题
为什么 Rollup 不打包图片/CSS?
Rollup 的核心理念是纯粹的 JavaScript 打包。图片、字体等资源需借助插件(如 rollup-plugin-styles、@rollup/plugin-image)才能处理,但库发布时一般不建议内联这些资源,通常留给应用层打包工具处理。
如何处理全局变量注入?
使用 @rollup/plugin-replace,可以像定义环境变量一样注入常量,例如 process.env.NODE_ENV。
TypeScript 支持?
安装 @rollup/plugin-typescript 和 tslib,在配置中加入即可直接打包 .ts 文件,同时也能享受 Tree-shaking。
为什么 bundle 中仍有未使用的 import?
检查是否存在副作用导入(如 import './polyfills'),或某模块包含顶级副作用。在 package.json 声明 "sideEffects": false 有助于 Rollup 更激进地移除代码,但必须确保整个项目无副作用,或精确列出有副作用的文件列表。
总结
Rollup 凭借其优异的 Tree-shaking 能力和清晰的输出,已成为 JavaScript 库打包的事实标准。通过掌握配置、选择合适的输出格式、结合关键插件,你可以构建出体积小巧、易于集成、且能被进一步优化的库。无论是开源项目还是公司内部组件库,Rollup 都是值得信赖的模块打包器。