Rolldown 1.0 深度实战:当 Vite 用 Rust 重写打包器,10–30 倍性能背后到底发生了什么?
2026 年 6 月,前端工程化圈被一条消息引爆:Rolldown 1.0 正式发布,作为 Vite 8 的默认打包器,构建速度比 Rollup 快 10–30 倍,同时保持 Rollup 插件 API 兼容(来源:JavaScript 技术周刊 2026 第 20 周)。这不是又一个"又快一点"的玩具,而是 Vite 团队(VoidZero)对前端构建范式的一次彻底重建。本文从架构、代码、性能三个维度,把 Rolldown 拆开给你看。
引子:一个被忽视的"构建之痛"
如果你是前端开发者,大概率经历过这样的场景:
本地
vite跑得好好的,一上线vite build就报错;或者反过来,构建产物里某个行为跟 dev 完全不一致,排查半天发现是打包器语义差异。
这个问题在 2023–2025 年的 Vite 用户群里几乎是人尽皆知的"暗伤"。它的根子不在你的代码,而在 Vite 的双引擎架构——dev 用 esbuild,build 用 Rollup。两套工具、两套语义,天然会漂移。
而当项目膨胀到几千个模块、几十 MB 依赖时,另一个痛点浮出水面:vite build 越来越慢。这个慢,本质上是 Rollup 跑在单线程 JS 里的物理极限。
Rolldown 要解决的,就是这两个痛点合起来的那个大问题——能不能有一个打包器,既有 Rollup 的插件生态和 tree-shaking 精度,又有 esbuild 量级的性能,还能让 dev 和 build 共用同一套语义?
答案是:用 Rust 把整个打包器重写一遍。
一、背景介绍:前端构建器的"双引擎"原罪
要理解 Rolldown 为什么重要,得先理解 Vite 过去三年的结构性尴尬。
1.1 Vite 的两条流水线
Vite 的设计哲学是"开发时用原生 ESM,生产时再打包"。为了兼顾"秒开"和"可上生产",它实际上维护了两套完全不同的工具链:
| 阶段 | 工具 | 语言 | 职责 |
|---|---|---|---|
| dev(开发) | esbuild | Go | 依赖预构建(pre-bundling)+ TS/JSX 转译,主打极速冷启动 |
| build(生产) | Rollup | JS | 生产打包,主打精细 tree-shaking 和成熟插件生态 |
这带来一个经典痛点:dev/prod 漂移(drift)。根因是 esbuild 和 Rollup 对以下语义的处理并不完全相同:
- ESM/CJS 互操作:同一个混合依赖,esbuild 和 Rollup 解析出的
require/import边界可能不一致。 - Side Effect 判定:
package.json的"sideEffects"字段,两边的处理粒度不同。 this绑定与全局对象:某些老 CJS 库依赖this === module.exports,在 ESM 严格模式下会变成undefined。
开发者管这叫"本地能跑、构建就挂"。根因不是你写错了,而是两个打包器在"最后一公里"的语义没对齐。
1.2 Rollup 的物理极限
更深一层,Rollup 本身跑在单线程 JS里。当项目膨胀后,它的几个环节会成为肉眼可见的瓶颈:
- 解析(acorn):纯 JS 的递归下降解析器,逐个文件同步解析,遇到大依赖图直接卡住。
- 作用域分析:用大量 JS 对象建模块图,分配/回收频繁,GC 压力大。
- 代码拼接(magic-string):用字符串切片 + 索引做源码映射,大文件会产生海量临时字符串。
社区里"Vite build 太慢"的抱怨,90% 归根结底是 Rollup 在 JS 单线程里的物理极限,而不是你的业务代码差。
1.3 问题的重新定义
于是问题被重新定义成一句话:
能不能有一个打包器,既拥有 Rollup 的插件生态和 tree-shaking 精度,又有 esbuild 量级的性能,还能让 dev 和 build 共用同一套语义?
Rolldown 就是 VoidZero(Vite / Vue 作者尤雨溪创立的公司)给出的答案。它不是一个从零拍脑袋的项目,而是 OXC 工具链成熟后水到渠成的产物。
二、核心概念:Rolldown 到底是什么
一句话定义:Rolldown 是一个用 Rust 写的 JavaScript/TypeScript 打包器,对外暴露与 Rollup 高度兼容的 API。
官方仓库的副标题说得更直白:Fast Rust bundler for JavaScript/TypeScript with Rollup-compatible API。
下面几个定位必须厘清,否则容易把它和 esbuild / webpack 搞混。
2.1 它是 Rollup 的替代品,不是 esbuild 的
很多人第一反应是"那它跟 esbuild 比谁快"。这个对比方向就错了。
- esbuild 快,但它的 tree-shaking 相对粗糙、插件模型简单,适合做"依赖预构建"和"快速转译",不适合做应用级生产打包的"最后一公里"。
- Rollup 慢,但 tree-shaking 精度高、插件生态成熟,是应用生产打包的事实标准。
Rolldown 的目标是把 Rollup 的位置整个替换掉——同样的 inputOptions / outputOptions / 插件钩子,但内核是 Rust。它要的是"Rollup 级的精度 + esbuild 级的速度"。
2.2 它构建在 OXC 之上
OXC(Oxidation Compiler) 是另一个 Rust 写的 JS/TS 工具链,提供极快的 parser(oxc_parser)、transformer、linter、minifier。你可以把它理解成"Rust 版的 Babel + ESLint + Terser 合体"。
Rolldown 的分工很清晰:OXC 负责"读懂代码"(解析 + 转译),Rolldown 自己负责"打包代码"(模块图、符号链接、分块、代码生成)。这种分层让 Rolldown 不必重复造解析器的轮子,直接吃到 OXC 的 Rust 级解析速度。
2.3 它向后兼容 Rollup 插件 API
这是 Rolldown 能"零摩擦"落地的关键。你写过的 resolveId / load / transform / renderChunk 钩子,几乎可以不改直接跑在 Rolldown 上。社区里大量现成的 Rollup 插件(如 @rollup/plugin-node-resolve、@rollup/plugin-commonjs、各种框架插件)可以平滑迁移。
官方在 1.0 发布时明确宣称"保持 Rollup 插件 API 兼容性"。这意味着存量的 Vite 项目不需要重写插件,就能切换到 Rust 引擎。
2.4 它顺手把 minify 也做了
借助 OXC 的 minifier(比 terser 快几个量级),Rolldown 可以在打包流程内部直接压缩,省掉原先 Rollup + terser 或 Rollup + esbuild 的两段式压缩。少一道 I/O、少一次 AST 往返,整体又省一笔时间。
2.5 性能数字怎么读
来自 2026 年 6 月 Rolldown 1.0 发布时的社区基准:比 Rollup 快 10–30 倍,早期采用者报告大型项目构建时间从"分钟级"降到"秒级"。
注意两个细节:
- 这个倍数是 vs Rollup,不是 vs esbuild。esbuild 本身也很快,但 Rolldown 在保有 Rollup 级精度的同时追平甚至超过 esbuild。
- "10–30 倍"是区间,取决于项目特征:模块越多、依赖图越复杂,Rolldown 的并行和 bitset 优势越明显,倍率越靠近 30;小而简单的项目,倍率会靠近 10。
三、架构分析:10–30 倍从哪里来
Rolldown 的构建流程可以抽象成三阶段流水线:模块扫描(Scan)→ 符号链接(Link)→ 代码生成(Generate)。我们逐段拆开,看它为什么快。
3.1 模块扫描:OXC 并行解析
入口确定后,Rolldown 用 OXC 把每个源文件解析成 AST。关键点在于:解析是天然可并行的——每个文件相互独立、互不依赖。
Rust 的多线程(rayon 风格的 work-stealing 线程池)可以把 N 个文件的解析摊到所有 CPU 核心上:
文件 A ──┐
文件 B ──┼─> [线程池并行解析] ──> N 棵 AST
文件 C ──┘
对比 Rollup:Rollup 用 acorn(纯 JS)逐个文件同步解析,JS 单线程 + 大量小对象分配 + GC 压力,光解析阶段就落后一个数量级。
3.2 符号链接:用 bitset 做可达性分析
解析完所有模块后,Rolldown 建立模块图(module graph),并做作用域分析与跨模块符号解析——这是 tree-shaking 的核心。它要回答一个根本问题:
从入口出发,哪些导出真正被用到?哪些代码是 dead code,可以安全删掉?
Rolldown 的巧妙之处在于用 bitset(位掩码) 表示模块 / 符号的可达性:
- 每个模块、每个导出符号占用一个 bit。
- "从入口可达"的传播,就是一串位运算(OR),而不是 Rollup 那种层层递归遍历对象图。
- 位运算在 CPU 上是纳秒级,且对缓存极度友好(一段连续内存,不是散落的 JS 对象)。
入口模块 bit=1
└─> 引用模块 A bit=1 (OR)
└─> 引用模块 B bit=1 (OR)
└─> 未引用模块 C bit=0 (被 tree-shake 掉)
这就是为什么 Rolldown 在"大型依赖图的 tree-shaking"上能甩开 Rollup——可达性分析从 O(对象图遍历) 变成了 O(位运算)。模块越多,差距越大。
3.3 代码生成:保留模式 + 智能分块
生成阶段 Rolldown 提供两种代码生成策略:
- 普通模式(normal):常规打包,按模块依赖顺序拼接成 chunk。
- 保留模式(advanced chunks):保留一定的模块结构边界,便于调试和长缓存。
真正的亮点是智能代码分割(code splitting)算法。它的逻辑可以概括为三步:
- 先处理开发者手动声明的
manualChunks(比如把react/vendor拆出来做长缓存)。 - 再自动优化剩余模块的分块,用 bitset 可达性信息把"总是一起被加载"的模块聚到同一 chunk。
- 最后合并冗余代码,消除跨 chunk 的重复。
整个过程兼顾"开发者的缓存控制权"和"打包体积 / 加载速度"。这正是前面搜索资料显示的核心机制:"通过位掩码标记模块可达性,先处理用户定义的手动分块,再自动优化剩余模块,最后合并冗余代码。"
3.4 Rust 内存模型的隐性红利
除了算法层面,Rust 本身带来三重红利,这些在 JS 版 Rollup 里是拿不到的:
- 无 GC 停顿:不像 JS 那样有不可预测的 GC 暂停,构建时间稳定可复现。
- 所有权 / 借用保证内存安全:编译期就排除了数据竞争,多线程并行零心智负担。
- 零拷贝字符串:Rolldown 内部用
String/Arc<str>做引用共享,避免了 Rollup 用magic-string反复切片拼接产生的大量临时字符串分配。
小结:"10–30 倍"的真实来源是一个组合拳——
并行解析(OXC)+ 位运算可达性(bitset)+ 无 GC 内存模型(Rust)+ 流程内压缩(OXC minifier)
任何单点都不是银弹,但四者叠加,就形成了对单线程 JS 版 Rollup 的代差。
四、代码实战:把 Rolldown 真正跑起来
光讲架构太空。下面用真实可运行的代码,从"直接打包"到"Vite 接入"到"库构建"逐层打通。
4.1 最小可用:用 Rolldown 直接打包一个库
先装包:
npm init -y
npm install rolldown
写一个最简单可跑的 Node API 示例(build.mjs):
// build.mjs
import { rolldown } from 'rolldown'
const bundle = await rolldown({
// 入口,支持多入口
input: ['./src/index.js'],
// 插件数组,这里先为空
plugins: [],
// 开启 Rolldown 内置的 oxc minifier
minify: true,
})
await bundle.write({
dir: './dist',
format: 'esm',
sourcemap: true,
})
console.log('✅ Rolldown build done')
配套一个最小源码:
// src/index.js
export function add(a, b) {
return a + b
}
export const PI = 3.1415926
跑 node build.mjs,你会在 dist/ 下得到 index.mjs 和 index.mjs.map。这就是 Rolldown 的最简闭环——API 形态和 Rollup 几乎一模一样。
4.2 写一个真正有意义的 Rollup 兼容插件
Rolldown 的卖点是"Rollup 插件 API 兼容"。下面写一个生产环境剥离 console.log 的插件,证明钩子可以直接复用:
// plugins/strip-console.mjs
/**
* @returns {import('rolldown').Plugin}
*/
export function stripConsole() {
return {
name: 'strip-console',
// transform 钩子:对每个模块源码做转换
transform(code, id) {
// 只对 JS/TS 源码生效,跳过 node_modules
if (!id.includes('node_modules') && /\.(js|ts|jsx|tsx)$/.test(id)) {
const stripped = code.replace(
/console\.(log|debug|info)\([\s\S]*?\);?/g,
'',
)
return {
code: stripped,
map: null, // 生产构建可不保留 sourcemap
}
}
return null
},
}
}
接入:
// build.mjs
import { rolldown } from 'rolldown'
import { stripConsole } from './plugins/strip-console.mjs'
const bundle = await rolldown({
input: ['./src/index.js'],
plugins: [stripConsole()],
minify: true,
})
await bundle.write({ dir: './dist', format: 'esm' })
注意:resolveId / load / transform / renderChunk 这些钩子的签名、执行顺序、返回结构都和 Rollup 一致。你从 Rollup 项目里搬来的插件,绝大多数不用改就能跑。
4.3 接入 Vite:让 dev 和 build 共用一套引擎
Vite 8 已经把 Rolldown 设为默认打包器。对于还在 Vite 5/6/7 的项目,社区提供 rolldown-vite 这个 drop-in 替换包:
npm install rolldown-vite --save-dev
然后在 package.json 里把 vite 的调用重定向到 rolldown-vite(或用它的 bin 覆盖):
{
"scripts": {
"dev": "rolldown-vite",
"build": "rolldown-vite build",
"preview": "rolldown-vite preview"
}
}
vite.config.ts 里你熟悉的 build.rollupOptions 此时会由 Rolldown 接管,语义完全一致:
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
build: {
// 原来的 rollupOptions 现在由 Rolldown 解释执行
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
},
},
},
// Rolldown 内置压缩,可省略独立的 minifier 插件
minify: 'rolldown',
},
})
迁移的最大收益不是"更快",而是dev 和 build 用同一套 Rust 引擎,语义彻底对齐——前面说的"本地能跑、构建就挂"类问题,会在架构层面消失。
4.4 用 tsdown 一键 Rust 化你的库构建
如果你做的是** npm 库**(不是应用),Rolldown 生态里有一个专门为此打造的工具:tsdown——构建在 Rolldown 之上的库打包器(2026 年 7 月已到 v0.11.4)。它把"库要出的 esm/cjs/dts、要做的 minify、要清理的 dist"都收敛成一个极简配置。
npm install tsdown --save-dev
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'], // 入口
format: ['esm', 'cjs'], // 同时出 ESM 和 CJS
dts: true, // 自动生成 .d.ts 类型声明
minify: true, // 用 Rolldown 内置 oxc minifier
clean: true, // 每次构建前清空 dist
sourcemap: true,
})
跑 npx tsdown,你会同时得到:
dist/
index.mjs # ESM,已压缩
index.cjs # CJS,已压缩
index.d.mts # ESM 类型声明
index.d.cts # CJS 类型声明
对比传统 tsup(基于 esbuild)或 tsc + rollup 的两段式流程,tsdown 用 Rolldown 一次性把解析、转译、打包、压缩、dts 全做了,速度提升显著,配置还更短。
4.5 实战:双模式打包 + 智能分块
真实项目里,你往往既要"应用入口"又要"拆 vendor 长缓存"。用 Rolldown 的 manualChunks 函数式写法可以精确控制:
// build.app.mjs
import { rolldown } from 'rolldown'
const bundle = await rolldown({
input: {
main: './src/main.js',
admin: './src/admin.js', // 第二个入口,自动 code-split
},
plugins: [],
})
await bundle.write({
dir: './dist',
format: 'esm',
sourcemap: true,
// 函数式 manualChunks:按路径把依赖分组
manualChunks(id) {
if (id.includes('node_modules')) {
if (id.includes('react')) return 'vendor-react'
if (id.includes('lodash') || id.includes('date-fns')) return 'vendor-utils'
return 'vendor' // 其余第三方统一进 vendor
}
},
})
这个配置会产出:
dist/
main.js # 业务主入口
admin.js # 管理后台入口(独立 chunk)
vendor-react.js # react/react-dom,长缓存
vendor-utils.js # 工具库,长缓存
vendor.js # 其余依赖
配合 HTTP 长缓存(Cache-Control: immutable),用户二次访问时 vendor 几乎不重新下载。这正是 Rolldown bitset 分块算法的用武之地:它能保证"总是一起被加载的模块"聚到同一 chunk,避免把不相关的代码强行塞进一个巨大的 vendor。
4.6 跑一个真实 benchmark,亲眼验证倍率
光看官方数字不够,自己跑一个对照实验最踏实。下面这个脚本在同一项目上分别用 Rolldown 和 Rollup 构建,算出倍率:
// benchmark.mjs
import { rolldown } from 'rolldown'
import { build as rollupBuild } from 'rollup'
import { rm } from 'node:fs/promises'
const ENTRY = './fixtures/big-app/src/index.js'
async function time(label, fn) {
const start = performance.now()
await fn()
const ms = Math.round(performance.now() - start)
console.log(`${label}: ${ms}ms`)
return ms
}
const tRolldown = await time('Rolldown', async () => {
const b = await rolldown({ input: ENTRY, minify: true })
await b.write({ dir: './dist-rolldown', format: 'esm' })
await b.close()
})
const tRollup = await time('Rollup', async () => {
const b = await rollupBuild({ input: ENTRY, plugins: [] })
await b.write({ dir: './dist-rollup', format: 'esm' })
await b.close()
})
console.log(`\nSpeedup: ${(tRollup / tRolldown).toFixed(1)}x`)
await rm('./dist-rolldown', { recursive: true, force: true })
await rm('./dist-rollup', { recursive: true, force: true })
跑在几百个模块、依赖密集的项目上,你大概率会看到 10–30x 的区间结果。模块越多、依赖图越大,倍率越靠近上限。
五、性能优化:把 30 倍真正吃到嘴里
Rolldown 快是默认的,但要吃满红利,还得避开几个常见坑。下面是一份可落地的优化清单。
5.1 用 Rolldown 内置 minify,别再叠 terser
很多老 Vite 项目配了 terser 做生产压缩。迁移到 Rolldown 后,直接用内置的 oxc minifier:
// vite.config.ts
export default defineConfig({
build: {
minify: 'rolldown', // 流程内压缩,省掉额外的 AST 往返
},
})
terser 是纯 JS,慢且单线程;oxc minifier 是 Rust,本身就快几个量级,还能和打包共用一份 AST。叠 terser 等于把前面省的时间又花回去。
5.2 用 external 避免重复打包
库项目(被别人 import 的 npm 包)一定要把 peer 依赖标 external,否则你会把 react 也打进你的包,体积爆炸且和宿主项目冲突:
// build.lib.mjs
import { rolldown } from 'rolldown'
const bundle = await rolldown({
input: ['./src/index.js'],
external: ['react', 'react-dom', 'vue'], // 交给使用方提供
})
await bundle.write({ dir: './dist', format: 'esm' })
5.3 大依赖用 manualChunks 拆长缓存
这点前面 4.5 演示过。核心原则:变动频率不同的代码要分到不同 chunk。框架(react/vue)几乎不变,业务代码天天变,拆开后用户二次访问只拉业务 chunk。
5.4 生产环境关掉不必要的 sourcemap
sourcemap: true 在开发很有用,但生产全量 sourcemap 既占空间又拖慢。生产可只保留 hidden 模式(生成 map 但不挂 //# sourceMappingURL)或干脆关闭:
await bundle.write({
dir: './dist',
format: 'esm',
sourcemap: false, // 生产关;出错时用 Sentry 等回源
})
5.5 让 dev 和 build 统一到 rolldown-vite
前面反复强调:用 rolldown-vite 替代 vite,让 dev 和 build 共用同一引擎。这本身不是"提速",而是消灭 dev/prod 漂移带来的隐性排错成本——这类 bug 往往比构建慢更折磨人。
5.6 用 watch 模式做增量构建
Rolldown 的 bundle.watch() 支持增量——只重新打包变动的模块图子树,配合 Rust 引擎,HMR 链路整体更顺:
const watcher = await bundle.watch({
dir: './dist',
format: 'esm',
})
// 需要时 watcher.close()
5.7 陷阱:插件兼容的灰色地带
Rolldown 兼容大多数 Rollup 插件,但有几类要小心:
- 重度依赖
this上下文或function钩子的插件:Rolldown 的钩子执行模型是 Rust 调度,部分依赖 JSthis动态的插件可能行为偏移。 - 直接操作 Rollup 内部 AST 对象形状的插件:Rolldown 内部是 Rust 结构,跨语言时做了映射,极少数依赖内部字段的插件要改。
- 依赖特定 magic-string 行为的插件:Rolldown 不用 magic-string,相关 hack 会失效。
迁移建议:先在小项目验证插件兼容性,再推全量。大部分主流插件(社区维护的)在 1.0 发布时已经适配。
六、总结展望:前端工程化进入"原生编译"时代
Rolldown 1.0 不是孤立事件,它和 2026 年前后的一系列动作属于同一股浪潮——前端工具链正在整体"原生化":
- TypeScript 7 用 Go 重写编译器(提速约 10 倍);
- Bun 用 Zig/Rust 重写 JS 运行时;
- LiteRT.js 用 WebGPU/WebNN/WASM 重构浏览器 ML;
- esbuild / SWC / OXC / Rolldown 用 Go / Rust 重写转译与打包;
- Vite 8 把默认打包器换成 Rolldown。
这条主线的逻辑非常清晰:当 JS 生态的规模膨胀到单线程 JS 工具处理不了时,唯一的解法是把工具本身下沉到系统级语言(Rust/Go/Zig),而把 JS/TS 留给业务。开发者再也不用为了"构建慢"去调一堆玄学参数,因为瓶颈已经从算法挪到了语言运行时。
对开发者的实际意义
- 构建不再是痛点:大型项目
vite build从分钟级降到秒级,CI 时长直接砍掉一截。 - dev/prod 一致性:同一引擎,漂移类 bug 在架构层面消失。
- 库作者零成本提速:用
tsdown一行配置,Rust 级构建自动到账。
需要保持清醒的地方
- 生态成熟度:Rolldown 1.0 虽已发布,但部分长尾 Rollup 插件的兼容仍在补齐,迁移前先验证。
- Vite 8 落地节奏:新项目直接上
rolldown-vite最稳;存量大仓建议灰度。 - 不是银弹:Rolldown 解决的是"打包"环节,网络传输、运行时性能、框架本身的运行时开销不归它管。
行动建议
- 新项目:直接
rolldown-vite+minify: 'rolldown',从第一天就吃满红利。 - 库项目:用
tsdown替代tsup/tsc+rollup,配置更短、速度更快。 - 存量应用:先在预发环境用
rolldown-vite跑一轮,确认插件兼容后再切生产。
写在最后
Rolldown 1.0 的意义,远不止"比 Rollup 快 30 倍"这个数字。它标志着 Vite 终于填上了"双引擎"这个存在了三年的结构性坑,也标志着前端构建正式进入 Rust 原生时代。对于每天被 vite build 卡进度条的我们来说,这是一次真正意义上的"苦尽甘来"。
下次当你 npm run build 秒回结果时,记得背后是 OXC 的并行解析、是 bitset 的位运算可达性、是 Rust 无 GC 的内存模型,在替你把时间一点点省回来。
参考资料(可溯源)
- Rolldown 官方仓库:github.com/rolldown/rolldown("Fast Rust bundler for JavaScript/TypeScript with Rollup-compatible API")
- JavaScript 技术周刊 2026 第 20 周(2026-06-12):Rolldown 1.0 发布,Vite 8 默认打包器,比 Rollup 快 10–30 倍,保持 Rollup 插件 API 兼容
- OXC 工具链:oxc.rs(Rust 版 parser / transformer / linter / minifier)
- tsdown(构建于 Rolldown 之上的库打包器):2026-07 已发布 v0.11.4