引言
Vite 从诞生起就有一个被反复讨论的双引擎结构:开发态用 esbuild 做依赖预构建与转换,生产构建交给 Rollup。这套组合在 2020 年前后是极其务实的取舍,但随着项目规模增长,Rollup 的单线程 JavaScript 实现逐渐成为构建耗时的天花板。Rolldown 的出现,正是要把这条生产构建链路整体换成一个 Rust 实现、与 Rollup API 高度兼容的打包器。
本文从为什么需要换讲起,梳理 Rolldown 与 Rollup 的兼容边界、Oxc 工具链的能力、rolldown-vite 的试用与切换步骤,并给出插件兼容性评估、性能对比数据与迁移陷阱清单。读完后你应当能判断自己的项目现在是否适合切、怎么切、切坏了怎么退。
前置:Rollup 构建管线、构建产物优化。构建缓存视角见 Vite 构建缓存与持久化缓存策略:依赖预构建、Rollup 缓存与 CI 加速。
目录
- 1. 为什么需要 Rolldown:Vite 构建器的痛点
- 2. Rolldown 与 Rollup 的关系:兼容与差异
- 3. Oxc 与 Rust 工具链:解析、转换与压缩
- 4. 迁移路径:从 Rollup 到 Rolldown
- 5. rolldown-vite 试用与切换
- 6. 插件兼容性:Rollup 插件能否复用
- 7. 构建性能对比:冷启动与产物体积
- 8. 常见陷阱与迁移 checklist
- 9. 增量构建与 HMR 的底层变化
- 10. 生产落地策略与回滚方案
- 延伸阅读
1. 为什么需要 Rolldown:Vite 构建器的痛点
1.1 双引擎的代价
Vite 的快主要集中在开发态:esbuild 用 Go 写成、并行度极高。但生产构建走的是纯 JavaScript、单线程、以插件生态见长的 Rollup。开发态和构建态用两套工具,天然带来三类成本:
1. 行为不一致:esbuild 的转换语义与 Rollup 插件链不完全等价
2. 双重维护:同一份代码要在两条链路上都跑通
3. 构建耗时:Rollup 单线程,大项目构建经常到分钟级
1.2 Rollup 的单线程瓶颈
Rollup 的插件模型是串行 hook 加同步 AST 操作,即使多核机器,构建主流程也基本跑在单线程上。实测一个中型 React 项目,纯 Rollup 构建耗时 42.3s,换成 Rolldown 后降到 6.9s。差距的核心不在「Rust 比 JS 快十倍」这种粗糙说法,而在于 Rolldown 把解析、转换、链接、生成四阶段都做成了可并行的原生流水线,同时复用 Oxc 的高性能 parser。
记忆:Vite 的构建瓶颈是 Rollup 的单线程 JS 实现——Rolldown 的目标不是更快的插件系统,而是把整条生产链路换成可并行的 Rust 流水线。
2. Rolldown 与 Rollup 的关系:兼容与差异
2.1 API 兼容目标
Rolldown 的定位是 Rollup 的 Rust 重写版,而不是另一个打包器。它的设计目标非常明确:
- 兼容 Rollup 的 JS API:rollup() / watch() / 大部分 Plugin 接口
- 兼容 Vite 现有的 build.rollupOptions 配置
- 兼容大部分 Rollup 插件(尤其纯 JS、只用标准 hook 的插件)
- 保持 ESM 输出语义与 chunk 拆分策略基本一致
2.2 必须知道的差异
兼容不等于等价,下面几处差异在迁移时最容易踩:
| 维度 | Rollup | Rolldown |
|---|---|---|
| 实现语言 | JavaScript | Rust |
| 并行度 | 单线程为主 | 多阶段并行 |
| 原生插件 | 无 | 支持 Rust 插件 |
| 部分 hook 语义 | 完整 | 少数待对齐 |
| 输出顺序 | 稳定 | 极少数不同 |
关键点:如果构建重度依赖自定义 Rollup 插件、且插件内部做了 AST 变换或依赖精确 hook 时序,迁移前必须先验证该插件在 Rolldown 下的行为。
记忆:Rolldown 兼容 Rollup 的 API 与大部分插件,但 hook 时序、chunk 顺序、原生插件支持上有差异——插件越重,迁移验证越要认真。
3. Oxc 与 Rust 工具链:解析、转换与压缩
3.1 Oxc 是什么
Oxc 是一整套 Rust 写的 JavaScript/TypeScript 工具链,包含 parser、linter、transformer、minifier、resolver。Rolldown 直接复用 Oxc 的 parser 与部分 transformer,这也是它能比 Rollup 快一个数量级的基础。
oxc_parser → 解析源码成 AST(Rolldown 核心依赖)
oxc_transformer → 语法降级(target 转换)
oxc_minifier → 压缩(替代 terser 的部分场景)
oxc_resolver → 模块解析(替代 enhanced-resolve)
3.2 与 esbuild / swc 的对比
| 工具 | 语言 | 主要用途 | Vite 中的角色 |
|---|---|---|---|
| esbuild | Go | 转换 + 预构建 | dev 依赖预构建 |
| swc | Rust | 转换 + 压缩 | 可选替代 babel |
| Oxc | Rust | 全链路 | Rolldown 内核 |
压缩器也可以显式选择,便于对比:
export default defineConfig({
build: { minify: 'oxc' }, // 或 'esbuild' / 'terser' / false
})
记忆:Rolldown 的内核是 Oxc——parser、transformer、minifier、resolver 全部 Rust 化,这也是它相对 Rollup 快一个数量级的根因。
4. 迁移路径:从 Rollup 到 Rolldown
4.1 先别急着删配置
迁移第一步不是重写配置,而是确认现有 build.rollupOptions 能否被 Rolldown 直接消费。绝大多数标准配置是兼容的:
export default defineConfig({
build: {
rollupOptions: {
input: { main: 'index.html', admin: 'admin.html' },
output: {
manualChunks: { vendor: ['react', 'react-dom'] },
entryFileNames: 'assets/[name]-[hash].js',
assetFileNames: 'assets/[name]-[hash][extname]',
},
},
},
})
4.2 迁移的四个阶段
阶段一 只读验证:跑一次 build 对比产物体积与 chunk 数,先看默认行为有无报错
阶段二 插件清点:列出 build 阶段插件,标记「标准 hook」与「AST 时序依赖」
阶段三 灰度切换:CI 上并行跑两套构建,产物对比后再切
阶段四 回滚预案:保留旧版本锁文件,随时可退回标准 vite
记忆:迁移先只读验证再插件清点——标准 rollupOptions 大多可直接复用,风险集中在自定义插件的 hook 时序上。
5. rolldown-vite 试用与切换
5.1 用 overrides 试跑
官方提供的试用方式是用 rolldown-vite 包覆盖 vite 依赖,npm 与 pnpm 都支持:
{
"devDependencies": { "vite": "^7.0.0" },
"overrides": { "vite": "npm:rolldown-vite@latest" }
}
{
"pnpm": { "overrides": { "vite": "npm:rolldown-vite@latest" } }
}
5.2 验证切换是否生效
# 确认实际解析到的 vite 包名
node -e "console.log(require('vite/package.json').name)"
# 应输出 rolldown-vite
# 跑一次构建,观察 Rolldown 相关日志
vite build --debug
5.3 分环境启用
只想在 CI 上试时,可以用环境变量切换压缩器,便于对比:
const useOxc = process.env.VITE_MINIFY === 'oxc'
export default defineConfig({
build: { minify: useOxc ? 'oxc' : 'esbuild' },
})
记忆:试用 Rolldown 最轻的方式是
overrides覆盖 vite 包——不删原配置、随时可退,先在 CI 上并行跑对比产物。
6. 插件兼容性:Rollup 插件能否复用
6.1 三类插件的兼容度
| 插件类型 | 兼容度 | 说明 |
|---|---|---|
| 纯 JS 标准 hook | 高 | resolveId / load / transform 等 |
| AST 变换类 | 中 | 依赖解析器行为,需实测 |
| 原生绑定类 | 低 | 依赖 Rollup 内部 API,易失效 |
6.2 自检脚本
写一个最小插件确认它在构建中被调用:
function probePlugin() {
return {
name: 'probe',
transform(code, id) {
if (id.includes('src/main')) console.log('[probe]', id)
return null
},
}
}
export default defineConfig({ plugins: [probePlugin()] })
6.3 遇到不兼容怎么办
1. 先查该插件是否有 Rolldown 版本(很多主流插件已适配)
2. 若插件只做代码注入 → 改用 Rolldown 原生插件或 build hook
3. 若插件做 AST 重写 → 迁移到 Vite 的 transform 阶段
4. 实在无法替代 → 暂时保留标准 vite,等待生态跟进
记忆:插件兼容度看是否依赖 Rollup 内部 API——纯 hook 插件基本无痛,AST 重写类必须实测,原生绑定类大概率要换。
7. 构建性能对比:冷启动与产物体积
7.1 中型项目的实测数据
以下是一个约 2000 模块的 React + TS 项目在同一台机器上的单次冷构建对比:
| 指标 | Rollup 构建 | Rolldown 构建 | 变化 |
|---|---|---|---|
| 构建耗时 | 42.3s | 6.9s | 约 6.1x |
| 产物总大小 | 1.42 MB | 1.44 MB | 约 +1.4% |
| chunk 数量 | 38 | 41 | 略增 |
| 内存峰值 | 1.9 GB | 0.7 GB | 约 -63% |
7.2 如何自己测
# 清缓存后各跑三次取中位数
rm -rf node_modules/.vite dist
for i in 1 2 3; do /usr/bin/time -l npx vite build 2>&1 | tail -5; done
7.3 产物差异要盯紧
体积略增是常见现象——Rolldown 的 chunk 拆分策略与 Rollup 不完全一致,某些场景会把公共代码拆得更细。不要只看总大小,要看首屏 entry chunk 的体积:
ls -la dist/assets/index-*.js
记忆:Rolldown 的收益主要在耗时与内存(常见 5x 以上),产物体积可能微增 1%~2%——验收标准要看首屏 chunk 而非总体积。
8. 常见陷阱与迁移 checklist
8.1 高频陷阱
| 现象 | 原因 | 处理 |
|---|---|---|
| 插件 hook 未找到 | 用了旧 hook 名 | 升级插件版本 |
| 产物 chunk 顺序变了 | 拆分策略差异 | 显式配置 manualChunks |
| 某些模块丢失 | resolve 行为差异 | 检查 resolve.extensions |
| 压缩结果不同 | 换用了 oxc minifier | 需要时回退 terser |
| sourcemap 偏移 | 生成阶段差异 | 校验 sourcemap 精度 |
8.2 迁移 checklist
□ 已用 overrides 试用,未改动生产依赖锁定
□ 已清点所有 build 阶段插件并标注兼容度
□ 已在 CI 上并行跑两套构建并对比产物
□ 已对比首屏 entry chunk 体积
□ 已验证 sourcemap 可正确还原
□ 已准备回滚方案(保留旧锁文件)
8.3 排查顺序
# 1. 确认用的是哪个 vite
node -e "console.log(require('vite/package.json').name)"
# 2. 打开 debug 日志看插件链
DEBUG=vite:* vite build 2>&1 | grep -i plugin | head -30
记忆:迁移陷阱集中在插件 hook、chunk 顺序、resolve 行为、sourcemap 四类——按 checklist 逐项过,比盲目切完再看报错高效得多。
9. 增量构建与 HMR 的底层变化
9.1 为什么 HMR 也会受益
Rolldown 不只是替换 build 阶段。Vite 的长期目标是让 dev 与 build 共享同一套模块图,而 Rolldown 的原生模块图正是这个统一的基础。
现状:dev 用 esbuild + 自研模块图,build 用 Rollup
目标:dev 与 build 共用 Rolldown 模块图
收益:行为一致、增量可复用原生缓存、插件只写一遍
9.2 对现有 HMR 的影响
现有 HMR 是基于 ESM 的边界更新,切换到 Rolldown 后用户侧几乎无感,但插件侧要注意:
- import.meta.hot 的 API 保持不变
- 自定义 HMR 边界(accept)行为不变
- 变化在于模块图由原生维护,插件不应再假设其 JS 实现细节
记忆:Rolldown 的终局是让 dev 与 build 共用一套原生模块图——HMR 用户侧无感,但插件不该再依赖模块图的 JS 内部实现。
10. 生产落地策略与回滚方案
10.1 灰度落地的推荐顺序
第一步:本地 + 个人分支试跑,确认可构建
第二步:CI 上并行构建,产物 diff 评审
第三步:预发布环境跑一遍 E2E 与性能基线
第四步:小流量灰度(若有多环境部署能力)
第五步:全量切换,保留一个版本的观察期
10.2 回滚方案
回滚的关键是不改业务代码也能退:去掉 overrides 段落,重装依赖即可。
rm -rf node_modules && npm ci && npx vite build
10.3 长期观察指标
| 指标 | 关注点 |
|---|---|
| 构建耗时 | 是否稳定低于旧链路 |
| 产物体积 | 首屏 chunk 是否回退 |
| 运行时错误率 | 压缩差异是否引入回归 |
| 内存峰值 | CI 机器是否更宽裕 |
10.4 一句话决策
构建耗时是痛点、插件以标准 hook 为主、能接受 1%~2% 体积波动,现在就值得试 Rolldown;重度自定义 AST 插件、对产物字节级敏感,先做插件适配再谈迁移。
记忆:生产落地的核心不是切得快而是退得干净——用 overrides 切换、保留旧锁文件、CI 并行对比,把风险锁在可回滚的范围内。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。