rolldown — 用 Rust 实现 Rollup 兼容协议的打包器
已复核rolldown 是用 Rust 写的 JavaScript / TypeScript 打包器。日常类比:对外仍是 Rollup 那套方向盘(input / output、plugin 钩子),发动机换成 Rust,parser / transform / minify 交给 oxc。
固定 v1.2.6 的目标是给 vite 当统一 bundler,也可单独当 Rollup 替代或需要更细 chunk 控制时的 esbuild 替代。维护方是 VoidZero。
npm i -D rolldownnpx rolldown src/main.js --file bundle.jsNode 引擎声明为 ^20.19.0 || >=22.12.0。预编译二进制按平台分发,缺失时回退 Wasm/WASI。
不理解 rolldown,下面这些事都没法解释:
- 为什么 Vite 8 的 npm 包已经依赖 rolldown,但 vite.dev 上仍能看到 Vite 7 的
rolldown-vite别名文档 - 为什么“Rollup plugin 能用”不等于每个钩子、每次跨语言调用都免费
- 为什么
generate()和write()不是同一个阶段 - 为什么模块解析不在 oxc 主仓,而在
oxc_resolver
-
流水线是 scan → link → generate(→ emit):
Bundler::write/generate先scan_modules(ScanStage),再LinkStage::link,再GenerateStage::generate。write随后建目录、写文件并跑writeBundle;generate停在内存。 -
Rollup 协议是兼容目标,不是逐钩子保证:文档写 plugin 接口 almost fully compatible,差异跟踪在上游 issue。热路径上的 JS
transform/resolveId仍要跨 Rust/JS;官方建议 hook filter。 -
内置能力比 Rollup 宽、比 esbuild 近:平台 preset、TS/JSX/syntax lowering、Node 解析、ESM/CJS interop、
define/inject、manual code splitting。define是 AST 级替换,和@rollup/plugin-replace的字符串替换不同。 -
oxc 是组件,不是整个仓库:workspace 钉死
oxc@0.147.0,resolver / sourcemap 是另两个 crate。增量构建只在experimental.is_incremental_build_enabled()打开时走incremental_*API。
案例 1:CLI 打一份 ESM
Section titled “案例 1:CLI 打一份 ESM”npx rolldown src/index.ts --file dist/bundle.js文档里的默认 output format 始终是 esm,与 platform 无关。platform 默认在 cjs 输出时为 node,否则为 browser。浏览器目标不会自动 polyfill Node 内建模块。
案例 2:JS API 先 generate 再 write
Section titled “案例 2:JS API 先 generate 再 write”import { rolldown } from "rolldown";
const bundle = await rolldown({ input: "src/main.js" });await bundle.generate({ format: "esm" });await bundle.write({ file: "bundle.js" });rolldown() 对应 Rust Bundler;generate 不落盘,write 才写。也有更短的 build(),默认写盘。watch().close() 返回 Promise,和 Rollup 同步 close 不同。
案例 3:最小 plugin,加上 filter 意识
Section titled “案例 3:最小 plugin,加上 filter 意识”import { defineConfig } from "rolldown";
export default defineConfig({ input: "src/index.ts", plugins: [{ name: "replace-foo", resolveId(source) { if (source === "foo") return "src/foo-replacement.ts"; return null; }, }],});resolveId 返回字符串表示接管,null 交给下一层。文档示例还警告:生产插件应使用 hook filter,减少每个模块都跳进 JS 的开销。
- 把
generate()当成已经写出 dist:只有write/build会fs.write。 - 用小 demo 断言“一定更快”:固定源码和文档都指向独立 benchmark 仓库;本页没有复跑。
- 把
define当字符串 replace:只能替换合法 identifier / member expression。 - 以为增量构建已经默认打开:
impl_bundler_build.rs把它关在 experimental flag 后面。 - 把 Vite 7 的
rolldown-vite别名文档当成 Vite 8 合同:npmvite@8.2.2已依赖rolldown ~1.2.4;本页绑定的是更新的1.2.6,并未审查 Vite 源码。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 需要 Rollup 级 plugin / chunk 控制,又想走 Rust + oxc 这条链
- 库或应用的独立 CLI/API 打包,接受 ESM 默认和 Node 20.19+ / 22.12+
- 对照 vite 8 如何把 bundler 收成一个依赖
不适用:
- 已经稳定的 webpack / rspack 配置,且没有统一引擎的痛
- 依赖尚未核验的冷门 Rollup plugin,或必须和 Rollup 逐钩子行为一致
- 需要本页提供的性能排名或 Vite 内部默认开关——那些要另绑 Vite revision
- 团队无法在出问题时读 Rust 阶段代码(scan/link/generate)
固定版本边界
Section titled “固定版本边界”- 本文绑定
rolldown/rolldown@5375362b...,tag 与 package 均为1.2.6。 - 同提交钉住
oxc@0.147.0、oxc_resolver@11.24.3、oxc_sourcemap@8.1.0。 - Node
^20.19.0 || >=22.12.0;缺失原生绑定可走 Wasm。 - 未安装依赖、运行上游测试、启动 Vite 或测量打包,状态保持
UNVERIFIED。
- 兼容旧协议比发明新 API 更容易带着生态走,但“almost compatible”必须对照差异表
- 统一引擎解决的是双工具语义分裂,不是自动消灭跨语言 plugin 成本
- scan / link / generate / emit 分开后,性能和正确性才能各自验收
- 依赖声明和文档站可能不同步——以可复查的 package / tag / SHA 为准
- 调用
bundle.generate({ format: "esm" })之后,磁盘上一定有产物吗? - 未打开 experimental incremental flag 时,
write()会走incremental_write吗? transform.define能像@rollup/plugin-replace那样替换任意子串吗?
检查点:
- 不一定。
generate只在内存里出 bundle;写盘是write。 - 不会。固定实现只在 flag 打开时走增量 API。
- 不能。它按 AST 替换 identifier / member expression。
- 官方文档:rolldown.rs
- 固定源码:rolldown/rolldown —— 本文绑定
5375362b36eeeaf514c67052ba65f3e97523dde5 - 审查记录:仓库内
docs/oxc-rolldown-source-review-20260827-o.md - oxc —— 本页钉住的 parser / transformer / minifier
- rollup —— plugin 协议基准
- vite —— 当前最大的集成消费者