Rollup 是一个 JavaScript 模块打包器,将小段代码编译成更大、更复杂的包。它是 JavaScript 库的首选打包器,以产生干净、高效的输出和出色的 tree-shaking 而闻名。Rollup 原生使用 ES 模块标准,这意味着它可以静态分析你的导入和导出,比依赖 CommonJS 的打包器更有效地消除死代码。如果你正在为 npm 构建库、框架组件或精简的应用包,Rollup 是一个值得掌握的出色工具。
TL;DR
Rollup 是一个针对库和精简应用优化的模块打包器。它提供一流的 tree-shaking,输出 ESM/CJS/UMD/IIFE,并拥有丰富的插件生态系统。当你需要干净、高效的包时使用 Rollup -- 特别是 npm 包。对于需要 HMR 的复杂应用,考虑使用 Vite(其生产构建底层使用 Rollup)。
关键要点
- 由于原生 ES 模块支持和卓越的 tree-shaking,Rollup 在主要打包器中产生最小的包。
- 插件生态系统(@rollup/plugin-*)覆盖所有需求:TypeScript、CommonJS 转换、Babel、压缩等。
- 单一配置的多输出格式(ESM、CJS、UMD、IIFE)使其成为库作者的理想选择。
- 带动态导入的代码分割支持应用构建的延迟加载。
- Vite 使用 Rollup 进行生产构建,因此 Rollup 插件在两个生态系统中都可用。
安装和入门
Rollup 可以全局安装或作为项目依赖安装。推荐的方法是本地安装并通过 npx 或 package.json 脚本使用。Rollup 支持 CLI 和配置文件两种用法。
# Install Rollup globally
npm install -g rollup
# Or as a dev dependency (recommended)
npm install --save-dev rollup
# Verify installation
npx rollup --version
# Bundle a file (CLI)
npx rollup src/index.js --file dist/bundle.js --format esm
# Bundle with a config file
npx rollup --config rollup.config.mjs
# Watch mode
npx rollup --config --watch配置文件
rollup.config.mjs 文件是配置 Rollup 的标准方式。它导出一个配置对象或配置数组。使用 .mjs 扩展名确保文件被视为 ES 模块。你可以定义单个或多个输出,这对需要同时发布 ESM、CommonJS 和 UMD 构建的库作者非常有用。
// rollup.config.mjs
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
sourcemap: true,
},
};
// Multiple outputs (library authors)
export default {
input: 'src/index.js',
output: [
{
file: 'dist/my-lib.esm.js',
format: 'esm',
sourcemap: true,
},
{
file: 'dist/my-lib.cjs.js',
format: 'cjs',
sourcemap: true,
},
{
file: 'dist/my-lib.umd.js',
format: 'umd',
name: 'MyLib',
sourcemap: true,
},
],
};核心插件
Rollup 在 @rollup/ 范围下拥有丰富的官方插件生态系统。最常用的插件包括:node-resolve(解析 node_modules 导入)、commonjs(将 CJS 转换为 ESM)、terser(压缩)、babel(转译)、typescript(TS 编译)和 json(导入 JSON 文件)。
// rollup.config.mjs with essential plugins
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import babel from '@rollup/plugin-babel';
import typescript from '@rollup/plugin-typescript';
import json from '@rollup/plugin-json';
export default {
input: 'src/index.ts',
output: {
file: 'dist/bundle.js',
format: 'esm',
sourcemap: true,
},
plugins: [
// Resolve node_modules imports
resolve({
browser: true,
preferBuiltins: false,
}),
// Convert CommonJS modules to ESM
commonjs(),
// Compile TypeScript
typescript({
tsconfig: './tsconfig.json',
}),
// Transpile with Babel
babel({
babelHelpers: 'bundled',
presets: [['@babel/preset-env', {
targets: '>0.25%, not dead',
}]],
exclude: 'node_modules/**',
}),
// Import JSON files
json(),
// Minify for production
terser({
compress: { drop_console: true },
}),
],
};Tree-Shaking(死代码消除)
Tree-shaking 是 Rollup 的标志性功能。因为 Rollup 原生理解 ES 模块的 import/export 语句,它可以静态确定哪些导出被使用并从最终包中移除未使用的代码。在 package.json 中设置 "sideEffects: false" 有助于 Rollup 消除整个未使用的模块。
// math.js - only used functions are included
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export function multiply(a, b) {
return a * b;
}
// index.js - only imports add
import { add } from './math.js';
console.log(add(2, 3));
// Rollup output: subtract and multiply are removed!
// This is tree-shaking in action.
// Mark side-effect-free modules in package.json:
// {
// "sideEffects": false
// }
//
// Or specify files with side effects:
// {
// "sideEffects": ["./src/polyfills.js", "*.css"]
// }代码分割和动态导入
Rollup 通过多入口点和动态 import() 表达式支持代码分割。使用代码分割时,输出必须使用 "dir" 选项而非 "file"。Rollup 自动为多个入口点共用的代码创建共享块。
// rollup.config.mjs - code splitting with multiple entry points
export default {
input: {
main: 'src/main.js',
admin: 'src/admin.js',
vendor: 'src/vendor.js',
},
output: {
dir: 'dist',
format: 'esm',
entryFileNames: '[name]-[hash].js',
chunkFileNames: 'chunks/[name]-[hash].js',
manualChunks: {
// Force specific modules into named chunks
lodash: ['lodash-es'],
react: ['react', 'react-dom'],
},
},
};
// Dynamic import for lazy loading
// src/main.js
async function loadChart() {
const { Chart } = await import('./chart.js');
return new Chart('#canvas');
}
document.getElementById('btn').addEventListener('click', loadChart);输出格式:ESM、CJS、UMD 和 IIFE
Rollup 支持四种主要输出格式。ESM 使用 import/export 语法,是浏览器和 Node.js 的现代标准。CJS 使用 require() 用于 Node.js 兼容性。UMD 在浏览器、Node.js 和 AMD 中通用。IIFE 用于直接 script 标签引入。
// ESM - Modern browsers and Node.js (recommended)
// Uses import/export syntax
{
output: {
file: 'dist/lib.esm.js',
format: 'esm', // or 'es'
}
}
// CommonJS - Node.js require() style
{
output: {
file: 'dist/lib.cjs.js',
format: 'cjs',
exports: 'auto', // 'default', 'named', 'none'
}
}
// UMD - Universal (browsers + Node.js + AMD)
{
output: {
file: 'dist/lib.umd.js',
format: 'umd',
name: 'MyLibrary', // global variable name
globals: {
react: 'React',
'react-dom': 'ReactDOM',
},
},
external: ['react', 'react-dom'],
}
// IIFE - Immediately Invoked Function Expression
// For <script> tags, no module loader needed
{
output: {
file: 'dist/lib.iife.js',
format: 'iife',
name: 'MyLibrary',
}
}库打包最佳实践
Rollup 擅长为 npm 打包库。关键原则:将所有依赖外部化、生成 ESM 和 CJS 输出、发出 TypeScript 声明文件、正确配置 package.json exports 映射。
// Complete library bundling config
// rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import terser from '@rollup/plugin-terser';
import { readFileSync } from 'fs';
const pkg = JSON.parse(
readFileSync('./package.json', 'utf8')
);
export default {
input: 'src/index.ts',
// Externalize peer dependencies
external: [
...Object.keys(pkg.peerDependencies || {}),
...Object.keys(pkg.dependencies || {}),
],
output: [
{
file: pkg.module, // "dist/index.esm.js"
format: 'esm',
sourcemap: true,
},
{
file: pkg.main, // "dist/index.cjs.js"
format: 'cjs',
sourcemap: true,
exports: 'named',
},
],
plugins: [
resolve(),
commonjs(),
typescript({ declaration: true, declarationDir: 'dist/types' }),
terser(),
],
};
// Matching package.json fields:
// {
// "main": "dist/index.cjs.js",
// "module": "dist/index.esm.js",
// "types": "dist/types/index.d.ts",
// "exports": {
// ".": {
// "import": "./dist/index.esm.js",
// "require": "./dist/index.cjs.js",
// "types": "./dist/types/index.d.ts"
// }
// },
// "files": ["dist"]
// }Watch 模式和开发工作流
Rollup 包含内置的 watch 模式,当源文件更改时自动重建。你可以配置要监视的文件、设置重建延迟,并使用编程 API 进行自定义构建管道。
// rollup.config.mjs with watch options
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
},
watch: {
include: 'src/**',
exclude: 'node_modules/**',
clearScreen: false,
// Rebuild delay (ms)
buildDelay: 100,
},
};
// package.json scripts
// {
// "scripts": {
// "build": "rollup -c",
// "dev": "rollup -c --watch",
// "build:prod": "NODE_ENV=production rollup -c"
// }
// }
// Programmatic API
import { watch } from 'rollup';
const watcher = watch({
input: 'src/index.js',
output: { file: 'dist/bundle.js', format: 'esm' },
});
watcher.on('event', (event) => {
if (event.code === 'BUNDLE_END') {
console.log('Built in ' + event.duration + 'ms');
}
if (event.code === 'ERROR') {
console.error(event.error);
}
});
// Close watcher when done
// watcher.close();高级配置
高级 Rollup 配置通常包括路径别名、环境变量替换、条件插件、包可视化、自定义警告处理器和 banner/footer 注入。
// Advanced rollup.config.mjs
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
import replace from '@rollup/plugin-replace';
import alias from '@rollup/plugin-alias';
import { visualizer } from 'rollup-plugin-visualizer';
const isProduction = process.env.NODE_ENV === 'production';
export default {
input: 'src/index.js',
output: {
dir: 'dist',
format: 'esm',
sourcemap: !isProduction,
banner: '/* My Library v1.0.0 */',
},
plugins: [
// Path aliases (like Webpack resolve.alias)
alias({
entries: [
{ find: '@', replacement: './src' },
{ find: '@utils', replacement: './src/utils' },
],
}),
// Environment variable replacement
replace({
preventAssignment: true,
'process.env.NODE_ENV': JSON.stringify(
process.env.NODE_ENV || 'development'
),
}),
resolve(),
commonjs(),
// Only minify in production
isProduction && terser(),
// Bundle visualization
isProduction && visualizer({
filename: 'stats.html',
gzipSize: true,
}),
].filter(Boolean),
// Suppress specific warnings
onwarn(warning, warn) {
if (warning.code === 'CIRCULAR_DEPENDENCY') return;
warn(warning);
},
};编写自定义插件
Rollup 插件遵循简单的基于钩子的 API。最重要的钩子包括:buildStart、resolveId、load、transform 和 generateBundle。
// Writing a custom Rollup plugin
function myPlugin(options = {}) {
return {
name: 'my-plugin',
// Called on each build start
buildStart() {
console.log('Build started...');
},
// Resolve custom import paths
resolveId(source) {
if (source === 'virtual:config') {
return source; // handle this id
}
return null; // let other plugins handle it
},
// Provide content for resolved ids
load(id) {
if (id === 'virtual:config') {
return 'export default { version: "1.0" };';
}
return null;
},
// Transform individual modules
transform(code, id) {
if (id.endsWith('.css')) {
return {
code: 'export default ' + JSON.stringify(code),
map: null,
};
}
},
// Post-processing the final bundle
generateBundle(options, bundle) {
for (const [name, chunk] of Object.entries(bundle)) {
console.log(name + ': ' + chunk.code.length + ' bytes');
}
},
};
}Rollup vs Webpack vs Vite
每个打包器有不同的优势。Rollup 产生最干净的输出,最适合库。Webpack 拥有最丰富的生态系统,最适合复杂应用。Vite 在生产中使用 Rollup,在开发中使用 esbuild,提供最佳开发体验。
| Feature | Rollup | Webpack | Vite |
|---|---|---|---|
| Primary use case | Libraries | Applications | Applications + DX |
| Tree-shaking | Excellent (native ESM) | Good (since v5) | Excellent (Rollup) |
| Dev server / HMR | Plugin needed | Built-in (WDS) | Built-in (instant) |
| Output cleanliness | Very clean | Wrapped in runtime | Clean (Rollup) |
| Config complexity | Simple | Complex | Minimal |
| Build speed | Fast | Moderate | Very fast (esbuild) |
| Code splitting | Yes | Yes (advanced) | Yes (Rollup) |
| Plugin ecosystem | Moderate | Very large | Growing (Rollup compat) |
| Bundle size | Smallest | Larger | Small (Rollup) |
最佳实践
- 构建库时始终将 peer 依赖外部化。将 React 等 peer 依赖打包到库中会导致消费应用中出现重复副本。
- 使用 package.json "exports" 字段的 "import" 和 "require" 条件来支持 ESM 和 CJS 消费者。
- 开发期间启用 sourcemap,但考虑在生产构建中禁用以减小输出大小。
- 当库没有副作用时,在 package.json 中设置 "sideEffects: false" 以启用最大化 tree-shaking。
- 使用 rollup-plugin-visualizer 分析包大小并识别优化机会。
- 优先使用 @rollup/plugin-typescript 而非 rollup-plugin-ts -- 官方插件维护更好、更可靠。
常见问题
什么时候应该使用 Rollup 而不是 Webpack?
构建 JavaScript 库、框架组件或任何发布到 npm 的包时使用 Rollup。Rollup 产生更干净、更小的包,具有更好的 tree-shaking。对于复杂应用使用 Webpack。对于新应用项目,Vite(底层使用 Rollup)通常是最佳选择。
Rollup 支持 TypeScript 吗?
支持。使用 @rollup/plugin-typescript 编译 TypeScript 文件。它与 tsconfig.json 集成并可以生成声明文件(.d.ts)。
Rollup 的 tree-shaking 如何工作?
Rollup 静态分析 ES 模块的 import/export 语句来确定哪些导出被使用。未使用的导出及其关联代码会从最终包中移除。
Rollup 能处理 CSS 和图片吗?
可以,通过插件。使用 rollup-plugin-postcss 处理 CSS,@rollup/plugin-image 处理图片文件。
ESM 和 CJS 输出有什么区别?
ESM 使用 import/export 语法,支持 tree-shaking 和静态分析。CJS 使用 require()/module.exports,是传统 Node.js 格式。ESM 是现代标准,应作为主要格式。
Vite 和 Rollup 是什么关系?
Vite 使用 Rollup 作为其生产打包器。开发期间 Vite 使用 esbuild 进行快速模块转换。Rollup 插件通常与 Vite 兼容。
Rollup 可以用于应用打包吗?
可以,但有注意事项。Rollup 可以处理代码分割和动态导入,但缺少内置 HMR 和开发服务器功能。对于应用,Vite 提供更好的开发体验。
如何处理 Rollup 中的循环依赖?
Rollup 支持 ES 模块中的循环依赖,但会发出警告。你可以使用配置中的 onwarn 处理器抑制特定警告。最佳实践是重构代码以消除循环依赖。