Rollup打包配置实战与性能优化技巧分享
先别管原理,直接跑起来再说
我第一次用 Rollup 的时候,根本没搞懂什么 Tree Shaking、ESM、CJS,就是项目里有个小工具库要打包,Webpack 太重了,同事说“试试 Rollup”,我就硬着头皮上了。结果发现:配置其实比想象中简单得多。
下面这个是最小可用配置,亲测有效,你复制过去就能跑:
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'es'
}
}
然后在 package.json 里加个脚本:
{
"scripts": {
"build": "rollup -c"
}
}
跑 npm run build,搞定。是不是比 Webpack 那堆 loader 简单多了?
这个场景最好用:打包一个 npm 包
Rollup 真正大放异彩的地方,是打包发布到 npm 的库。比如你写了个 utils 工具集,想让别人通过 import { debounce } from 'my-utils' 引入,这时候 Rollup 是首选。
关键点来了:你要同时输出多种模块格式。为什么?因为用户可能用 ES 模块(Vite/现代浏览器)、CommonJS(Node.js 或老项目)、甚至 UMD(直接 script 标签引入)。所以 output 最好这么配:
// rollup.config.js
export default {
input: 'src/index.js',
output: [
{
file: 'dist/my-utils.es.js',
format: 'es'
},
{
file: 'dist/my-utils.cjs.js',
format: 'cjs'
},
{
file: 'dist/my-utils.umd.js',
format: 'umd',
name: 'MyUtils' // UMD 全局变量名
}
]
}
这样一套打下来,基本覆盖所有使用场景。我在几个开源小工具里都这么干,社区反馈都没问题。
踩坑提醒:这三点一定注意
别看 Rollup 配置简单,但有几个坑我踩过不止一次,这里必须重点提醒:
- 不处理 JSON 和 CSS:Rollup 默认只认 JS。如果你的代码里有
import data from './config.json',直接报错。解决办法是装插件:@rollup/plugin-json。同理,CSS 需要@rollup/plugin-node-resolve+rollup-plugin-postcss之类的组合。别偷懒,该装就装。 - 第三方依赖默认不打包:Rollup 默认会把 node_modules 里的包 external 掉,也就是不打进 bundle。这在打包库的时候是对的(让用户自己装依赖),但如果你是要打一个独立应用(比如命令行工具),就得手动关掉 external。我曾经打包一个 CLI 工具,结果运行时报 “Cannot find module ‘lodash’”,折腾半天才发现是因为 lodash 被 external 了。解决方法是在配置里加:
external: [],或者更精细地控制哪些要打包。 - 路径别名容易翻车:很多人喜欢用
@/utils这种别名。Rollup 本身不支持,得靠@rollup/plugin-alias。但要注意,这个插件的配置顺序很重要,必须放在@rollup/plugin-node-resolve前面,否则 resolve 插件先跑,别名还没生效,直接找不到模块。我在这上面浪费过至少两小时。
实战配置:带 Babel 和压缩的完整例子
光能跑还不够,生产环境肯定要转译和压缩。下面是我现在项目里用的配置,已经稳定跑了半年多:
// rollup.config.js
import babel from '@rollup/plugin-babel';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import terser from '@rollup/plugin-terser';
export default {
input: 'src/index.js',
output: [
{
file: 'dist/lib.es.js',
format: 'es',
sourcemap: true
},
{
file: 'dist/lib.cjs.js',
format: 'cjs',
sourcemap: true
}
],
plugins: [
json(),
nodeResolve({
browser: true
}),
commonjs(),
babel({
babelHelpers: 'bundled',
presets: ['@babel/preset-env']
}),
terser() // 压缩
],
external: ['react', 'react-dom'] // 如果是 React 组件库,这些通常 external
}
几点说明:
babelHelpers: 'bundled'很关键,不然 Babel 生成的 helper 函数(比如 _classCallCheck)会重复出现在每个文件里,体积爆炸。terser()放最后,压缩前的所有步骤都走完再压。- 如果只是纯 JS 工具库,可以去掉 Babel,直接用现代语法,让使用者自己处理兼容性——现在很多库都这么干了。
高级技巧:动态生成多个入口
有时候你不是一个入口,而是多个组件要分别打包,比如一个 UI 库里有 Button、Modal、Toast 各自独立导出。这时候可以用 Rollup 的数组配置:
// rollup.config.js
import fs from 'fs';
import path from 'path';
const components = fs.readdirSync('src/components')
.filter(f => fs.statSync(path.join('src/components', f)).isDirectory());
const configs = components.map(comp => ({
input: src/components/${comp}/index.js,
output: {
file: dist/${comp}.js,
format: 'es'
},
plugins: [/* 你的插件 */]
}));
export default configs;
这样每个组件单独打包,用户按需引入时不会把整个库都带进来。不过要注意,公共依赖(比如 utils)可能会被重复打包。如果组件间共享逻辑多,建议还是单入口 + Tree Shaking 更稳妥。
别迷信 Tree Shaking,它没那么智能
很多人以为用了 Rollup 就自动瘦身,其实 Tree Shaking 有很多限制。比如:
- 必须用 ES 模块语法(import/export),CommonJS 不行
- 不能有副作用的代码(比如顶层执行的函数调用),否则 Rollup 不敢删
- 动态 import 或 require() 会导致整个模块被保留
我之前有个工具函数写了 console.log('init') 在顶层,结果整个文件都没被摇掉。后来加上 /*#__PURE__*/ 注释才解决:
const result = /*#__PURE__*/ heavyComputation();
所以别指望开箱即用,得配合代码写法。实在不确定,就看生成的 bundle,手动检查有没有不该有的代码。
结尾碎碎念
Rollup 不是银弹,但对于库作者来说,它依然是目前最顺手的打包工具。配置灵活、生态成熟、输出干净。我现在的几个 npm 包全靠它撑着,CI/CD 流水线跑得稳稳的。
以上是我踩坑后的总结,希望对你有帮助。这个技术的拓展用法还有很多(比如插件开发、与 TypeScript 深度集成),后续会继续分享这类博客。如果你有更好的实践,欢迎评论区交流——毕竟前端工具链这东西,三天不看就过时了。

暂无评论