编程 Rolldown 深度拆解:当 Rust 重写打包器成为 Vite 8 的统一底座——从 Rollup 兼容 API 到 esbuild 特性对齐与 19k 模块基准的全链路实战

2026-08-18 07:45:30 +0800 CST views 7

Rolldown 深度拆解:当 Rust 重写打包器成为 Vite 8 的统一底座——从 Rollup 兼容 API 到 esbuild 特性对齐与 19k 模块基准的全链路实战

一句话结论:Rolldown 不是又一个「更快的打包器」,它要做的是把 Vite 今天 dev 用的 esbuild 和 build 用的 Rollup 坍缩成同一个 Rust 引擎——对外保留 Rollup 的插件写法,对内吃下 esbuild 的速度。这才是 Vite 8 真正的底座革命。

0. 引子:前端构建的「双引擎」之痛

如果你用过 Vite,你应该有过这种体验:本地 dev 跑得好好的,一上 build 就报错;或者某个依赖在 dev 下被 esbuild 预打包成了另一种形态,到了生产构建里行为微妙地变了。

这不是你的 bug,是 Vite 当前架构的「原罪」:它今天同时在用两个打包器

  • dev server 启动时,Vite 用 esbuildnode_modules 里的依赖做预打包(pre-bundling),把 CommonJS 拆成 ESM、把 lodash 之类的大包聚合成少数几个文件,避免浏览器发起成百上千个请求。
  • 生产构建时,Vite 切回 Rollup,做严谨的 tree-shaking、scope hoisting、代码分包,产出最终产物。

两套引擎、两套解析器、两套插件模型(esbuild 的 plugin 和 Rollup 的 plugin 完全不是一回事)。dev 和 build 对同一个 import 的理解可能差之毫厘,于是「在我机器上 dev 好好的,build 挂了」成了前端工程师心照不宣的痛。

Rolldown 的出现,就是要把这「两条心」并成「一条心」。

1. 背景介绍:打包器的四次浪潮

要理解 Rolldown 的位置,得先看清前端构建这十年的四次浪潮。

第一波:webpack 一统天下(2014–2019)。 webpack 用「一切皆插件、一切皆 loader」的极致可扩展模型,吃下了整个前端工程化——HMR、code splitting、资源处理、环境变量注入,几乎所有需求都能用 plugin 解决。代价是:它太重、太慢,配置即天书,node_modules 一膨胀,冷启动以分钟计。

第二波:Rollup 成为库打包标杆(2016 至今)。 Rollup 提出 ESM 优先 + 基于 ES Module 静态分析的 tree-shaking,能把一个库里没用到的导出整段删掉,输出干净到极致。它的 scope hoisting(作用域提升)能把几十个模块的函数合并进同一个作用域,消除模块间函数调用开销。今天绝大多数 JS 库(包括 Vue、React、Vite 自身)的生产构建都跑在 Rollup 上。但它用纯 JS 写,单线程遍历模块图,面对万级模块时慢得离谱。

第三波:esbuild 用 Go 撕开速度天花板(2020 至今)。 esbuild 用 Go 重写,把打包速度提升了 10–100 倍,冷启动以毫秒计。但它的设计哲学是「快就好」,tree-shaking 粒度、对 ESM 边界和 side-effect 的处理、插件模型都比 Rollup 糙一截——它适合做「快速转换/压缩」,不适合做「严谨的生产打包」。

第四波:Rust 工具链全面接管(2022 至今)。 SWC 用 Rust 重写 Babel,Rspack 用 Rust 重写 webpack,Biome 用 Rust 重写 ESLint+Prettier,Tailwind 的 Oxide 引擎用 Rust 重写,而 Rolldown 则是用 Rust 重写 Rollup——并且顺手把 esbuild 的特性也对齐了。

Rolldown 不是凭空出现的野路子。它背后是 VoidZero(Evan You 创立的公司,Vite 的商业实体),和 OXC(同一团队的 Rust 前端工具链)是亲兄弟。官方站点 rolldown.rs 上写得很直白:“The unified bundler powering Vite 8+”。它从出生就被设计成 Vite 下一代的统一引擎。

为什么是「现在」而不是三年前?因为 Rust 前端工具链的底层终于成熟了。三年前你用 Rust 写个打包器,得自己造 parser、自己造 sourcemap、自己造 CJS 互操作层,成本劝退;而今天 OXC 把 parser/transform/sourcemap 全做成可复用的 Rust crate,napi-rs 把「Rust 核心 + Node 绑定」的工程模板跑通,SWC/Rspack 又在前面试错了打包器的 Rust 化路径。Rolldown 是站在这一整条成熟链条肩膀上的产物——它省掉了造轮子,只专注「把 Rollup 语义在 Rust 里重做对」这一件事。

2. 核心概念:Rolldown 到底是什么

给 Rolldown 一个精确的定义:

Rolldown 是一个用 Rust 编写的 JavaScript/TypeScript 打包器,对外提供与 Rollup 兼容的 API 和插件接口,同时补齐 esbuild 级别的特性(内置 transform、define、inject、minify)。

三个关键词,缺一不可:

① Rollup-compatible(与 Rollup 兼容)。 这意味着你写给 Rollup 的插件,几乎零成本就能在 Rolldown 上跑。Vite 生态里成千上万的 rollup 插件(vite-plugin-xxx 本质上都是 rollup 插件),不用改一行就能复用。这是 Rolldown 能「上车即替换」的根本原因——它不要求整个生态重写。

② esbuild feature parity(esbuild 特性对齐)。 Rolldown 内置了 transform(TS/JSX 转译)、define(常量替换)、inject(全局变量注入)、minify(压缩)。换句话说,你不再需要外接 esbuild 来做压缩和转译了,一个引擎全包。这正是「统一」二字的字面含义。

③ Designed for Vite(为 Vite 而生)。 它是 Vite 8 的统一打包器,一个引擎同时接管 dev 的依赖预打包和生产构建。

理解了定义,再看 Rolldown 脑子里运转的几个核心概念——这些和 Rollup 一脉相承,但执行在 Rust 里:

  • Module Graph(模块图):从 entry 出发,按 import/export 关系构建一张有向图。每个节点是一个模块,每条边是一次依赖。
  • Scope Hoisting / Tree-shaking:基于 ESM 的静态结构,分析哪些导出被真正使用,把没用到的代码(以及没用到的模块)整段删掉,再把保留下来的函数合并进同一作用域,减少运行期函数调用开销。
  • Plugin Pipeline(插件管线)resolveId(解析模块路径)→ load(读取模块内容)→ transform(转换代码)→ renderChunk/generateBundle(产物阶段处理)。Rolldown 完整复刻了 Rollup 的钩子顺序。
  • Code Splitting(代码分包):遇到动态 import() 自动拆出独立 chunk,并抽离共享依赖到公共 chunk。
  • Output formats(输出格式):esm / cjs / iife / umd,覆盖库打包和浏览器运行的全部场景。

2.1 Rolldown 在打包器坐标系里的精确位置

把四个常被拿来比较的名字放一张表,你就知道 Rolldown 卡位的精妙:

工具语言兼容对象擅长短板
RollupJS最严谨 tree-shaking、库打包标杆慢(单线程 JS 遍历)
esbuildGo极速转换与压缩tree-shaking 糙、插件模型弱
RspackRustwebpackwebpack 生态 / Loader 平迁不是 Rollup 语义
RolldownRustRollup + esbuildRollup 严谨 + esbuild 速度生态仍在生长

一句话总结:Rspack 解决的是「webpack 用户怎么无痛换 Rust」,Rolldown 解决的是「Rollup / Vite 用户怎么无痛换 Rust、并且顺手吃掉 esbuild」。两者不冲突,分别卡住前端的两大存量阵营——一个是 webpack 体系,一个是 Rollup/Vite 体系。理解了这张表,你就不会再把 Rolldown 和 Rspack 当成「二选一」的竞品,而会按自己项目站在哪个阵营来选。

3. 架构分析:为什么 Rust 能既「Rollup 兼容」又「esbuild 快」

这是本文最值得嚼的部分。Rolldown 的巧妙,在于它把「兼容」和「快」分开了层。

3.1 技术栈:OXC 打底,napi-rs 接桥

Rolldown 的解析和转换建立在 OXC 之上——oxc_parser 做词法/语法解析,oxc_ast 做 AST,oxc_transform 做 TS/JSX 转译,oxc_sourcemap 做 sourcemap 映射。OXC 是 VoidZero 同一团队的 Rust 前端工具链,用 SIMD 和零拷贝把解析这一步榨到极致。

Rust 核心通过 napi-rs 编译成 Node 原生插件。你在 JS 里写 import { rolldown } from 'rolldown',拿到的其实是对 Rust 核心的一层薄封装。模块图构建、解析、转换、tree-shaking、压缩——这些重活全部在 Rust 的多线程里跑完,JS 侧只负责「发号施令」和「写插件」。

3.2 与 Rollup 的本质区别:重活不回 JS

Rollup 是 JS 写的,插件用 JS 回调驱动,模块图的遍历跑在单线程的 JS 事件循环里。哪怕你的插件只是「读改字符串」,Rollup 也必须在 JS 里把整张图走一遍。

Rolldown 把整条链路搬进 Rust:模块解析、AST 转换、依赖分析、tree-shaking、压缩、sourcemap 生成,全部在 Rust 的多线程世界里完成。插件钩子仍然兼容 Rollup 的 JS 接口(通过 napi 跨语言边界调用),但「遍历图」「算副作用」「删死代码」这些 CPU 密集的事,一次都不回 JS。

代价是什么?如果你的插件在 transform 里做极度密集的同步 JS 计算,每次调用都要跨一次 napi 边界,会有一点点开销。但对绝大多数「读字符串、改字符串、返回字符串」的插件来说,这个开销小到可以忽略。

3.3 与 esbuild 的本质区别:速度 + 严谨兼得

esbuild 快,但「不 Rollup」。它的 tree-shaking 粒度、对 ESM 边界和 side-effect 标记的尊重程度、插件模型,都比 Rollup 糙。所以在生产场景里,大家更愿意用 Rollup 而非 esbuild 做最终打包。

Rolldown 的野心正是把 esbuild 的速度Rollup 的严谨 合二为一:用 Rust 拿到 esbuild 那级别的速度,用 OXC + Rollup 兼容的语义分析拿到 Rollup 那级别的 tree-shaking 精度。

3.4 「统一打包器」到底统一了什么

回到开头的痛点。Vite 当前是 esbuild(dev 预打包)+ Rollup(build)。Rolldown 一个引擎替换掉两者之后:

  1. 行为一致:dev 和 build 用同一套解析、transform、resolve 逻辑,同一个 import 在两种模式下理解完全一致,「dev 好 build 挂」的概率大幅降低。
  2. 插件一套写两处免:不用再为 dev 写 esbuild plugin、为 build 写 rollup plugin。
  3. 冷启动更快:连 esbuild 预打包那一步都被统一进 Rust,dev server 启动链路更短。

这就是「统一底座」四个字的全部重量。

3.5 基准架构:19k 模块下 Rolldown 逼近 esbuild

官方 rolldown.rs 跑了一个很有说服力的基准:19k 个模块(10k 个 React JSX 组件 + 9k 个 iconify 风格的 JS 文件),开启 minify 和 sourcemap:

工具耗时
Rolldown1.61s
esbuild1.70s
rspack4.07s
Rollup + esbuild40.10s

怎么读这张表:

  • Rollup + esbuild(也就是今天 Vite 生产构建的链路)慢在 40 秒——瓶颈是 Rollup 用单线程 JS 构建那张 19k 节点的模块图。
  • Rolldown 在 Rust 里把整条链路一口吃掉,干到了 1.61s,甚至比纯 esbuild 还略快一点,同时保留了 Rollup 级的 tree-shaking 精度。
  • rspack 作为 Rust 版 webpack,4 秒多,说明「Rust 重写」不是银弹,架构设计(是否真的把图构建搬进原生层)才是关键。

3.6 深入:模块图如何在 Rust 里并行遍历

理解 Rolldown 为什么快,得拆开看它怎么走这张图。模块处理天然分成两段:

  • 解析阶段(可并行):每个源文件彼此独立,读取 → OXC 解析成 AST → transform 钩子转译,这些事之间没有依赖,天然可以摊到所有 CPU 核上。Rolldown 用工作窃取(work-stealing)调度器把模块任务丢进线程池,哪个核空闲就抢哪个核的活。
  • 链接阶段(有依赖顺序):等所有模块都变成 AST 后,才按 import/export 关系做符号绑定、副作用分析、tree-shaking。这一段有先后依赖,但计算量远小于解析,且 Rust 的内存模型和并行迭代器让它比 Rollup 的单线程 JS 遍历快得多。

对比 Rollup:它用纯 JS 单线程把「解析 + 链接」串在一根调用栈上跑,19k 个模块就是 19k 次同步 JS 函数调用叠出来的 40 秒。Rolldown 把最重的解析搬进 Rust 并行世界,JS 侧只剩下薄薄的插件钩子调用——这就是 1.61s 对 40.10s 鸿沟的根源。

4. 代码实战:从最小构建到复现基准

光说不练假把式。下面全部是可运行代码。

4.1 最小构建(JS API)

// build.mjs
import { rolldown } from 'rolldown'

const bundle = await rolldown({
  input: 'src/main.js',
  // 这里是兼容 Rollup 的插件数组
  plugins: [
    // 你写给 Rollup 的插件直接塞进来
  ],
})

await bundle.write({
  dir: 'dist',
  format: 'esm',
  minify: true,      // ★ esbuild 特性对齐:内置压缩,无需再外接 esbuild
  sourcemap: true,
})

注意 minify: true——这是 Rolldown 基于 OXC minifier 的内置压缩,不再需要 esbuild.minify()terser 当外挂。这就是「esbuild feature parity」最直观的体现。

4.2 接入 Vite:rolldown-vite 零改造成本

这是最香的部分。Vite 团队提供了 rolldown-vite,它是 Vite 的 drop-in 替换,把底层打包器从 Rollup 换成 Rolldown,而你的配置和插件几乎不动:

// vite.config.js
import { defineConfig } from 'rolldown-vite'  // ★ 只换这一行的来源

export default defineConfig({
  // 原有 Vite 配置原封不动
  plugins: [
    // 你的 rollup 插件照用,无需改写
  ],
})
// package.json
{
  "scripts": {
    "dev": "rolldown-vite",
    "build": "rolldown-vite build"
  }
}

换掉 binary 和 import 来源,剩下的交给 Rolldown。你的 vite-plugin-* 生态原样运行。

4.3 写一个 Rollup 兼容插件:虚拟模块 + 环境变量注入

为了证明「兼容」不是口号,我们手搓两个插件,写法和 Rollup 100% 一致。

// plugins.mjs

// 插件一:把 import 'virtual:config' 映射到一个由环境变量生成的虚拟模块
const envConfigPlugin = (env) => ({
  name: 'env-config',
  resolveId(id) {
    // \0 前缀是 Rollup/Rolldown 约定的「虚拟模块」标记
    if (id === 'virtual:config') return '\0virtual:config'
  },
  load(id) {
    if (id === '\0virtual:config') {
      const cfg = {
        apiBase: env.API_BASE ?? '/api',
        version: env.VERSION ?? 'dev',
      }
      return `export default ${JSON.stringify(cfg)}`
    }
  },
})

// 插件二:注入 import.meta.env 风格的环境常量(类似 Vite 的 define)
const definePlugin = (defines) => ({
  name: 'mini-define',
  transform(code, id) {
    if (!id.endsWith('.js') && !id.endsWith('.jsx')) return null
    let out = code
    for (const [key, value] of Object.entries(defines)) {
      // 把源码里的 __API_BASE__ 之类的占位符替换成字面值
      out = out.split(key).join(JSON.stringify(value))
    }
    return out
  },
})

export { envConfigPlugin, definePlugin }

这两个插件的 resolveId / load / transform 钩子签名、返回值语义、虚拟模块的 \0 约定,和 Rollup 文档里写的完全一致。把它们塞进 rolldown({ plugins: [...] }) 就能跑。这就是「兼容」的含金量。

4.4 Rust 核心 API(库嵌入场景)

Rolldown 也能作为 Rust 库被嵌入到其他 Rust 程序里——比如你在写一个需要内置前端构建能力的 CLI:

// src/main.rs —— 示意,API 以官方 rolldown.rs 为准
use rolldown::Bundler;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut bundler = Bundler::new();
    let _outputs = bundler
        .write(
            rolldown::InputOptions {
                input: vec!["src/main.js".into()],
                ..Default::default()
            },
            rolldown::OutputOptions {
                dir: Some("dist".into()),
                format: rolldown::OutputFormat::Esm,
                ..Default::default()
            },
        )
        .await?;
    Ok(())
}

注:Rust API 仍在演进,具体字段请以 rolldown.rs 官方文档为准;这里展示的是「可作为库嵌入 Rust 程序」这一能力方向。

4.5 复现官方基准:生成 19k 模块并计时

把 3.5 那张表在自己机器上跑出来,才算真懂。

// gen.mjs —— 生成 10k React 组件 + 9k iconify 风格模块
import { writeFileSync, mkdirSync } from 'node:fs'
mkdirSync('bench/src', { recursive: true })

for (let i = 0; i < 10000; i++) {
  writeFileSync(
    `bench/src/C${i}.jsx`,
    `import React from 'react'\n` +
    `export const C${i} = () => <div className="c${i}">C${i}</div>\n`
  )
}
for (let i = 0; i < 9000; i++) {
  writeFileSync(
    `bench/src/I${i}.js`,
    `export const I${i} = { name: 'icon-${i}', path: 'M0 0h${i}' }\n`
  )
}
const imports = Array.from({ length: 10000 }, (_, i) => `import { C${i} } from './C${i}'`).join('\n')
const exports = 'export { ' + Array.from({ length: 10000 }, (_, i) => `C${i}`).join(',') + ' }\n'
writeFileSync('bench/src/main.js', imports + '\n' + exports)
// run.mjs —— 计时对比
const t = Date.now()
const { rolldown } = await import('rolldown')
const bundle = await rolldown({ input: 'bench/src/main.js' })
await bundle.write({ dir: 'bench/dist', format: 'esm', minify: true, sourcemap: true })
console.log('rolldown:', ((Date.now() - t) / 1000).toFixed(2), 's')

拿到 1.6s 量级(取决于你机器核数),说明 Rolldown 的 Rust 并行确实到位了。想对比的话,把同一份 bench/src 喂给 rollup + esbuild,你会亲眼看到那 40 秒的鸿沟。

5. 性能优化:榨干 Rolldown 的几个旋钮

Rolldown 默认已经很快,但要知道往哪拧才能把它推到极限,也知道哪些「自以为的优化」其实是坑。

① 并行是默认项,别乱配 worker。 Rust 核心会自动用多线程吃满 CPU,你不需要像 webpack 那样手动开 thread-loaderparallel。相反,如果你在插件里搞了重度同步 JS 计算,反而会因为频繁跨越 napi 边界而拖慢整体——把重活尽量放进构建前(预生成文件)或 Rust 侧。

minify 选型。 内置的 OXC minifier 已经很快且够用。只有在需要 terser 级别的极致压缩(比如还要兼容老 IE、要特定的压缩语义)时,才考虑后置 terser——但那会牺牲速度,权衡清楚。

③ 指定 target 在输出选项里指定目标 ES 版本(如 'es2020'),压缩器就能更激进地删掉 polyfill、用更短的语法。面向现代浏览器时,这一项能同时减小体积和提升压缩速度。

④ 分包策略。output.manualChunks 把 vendor 稳定拆出来,配合 HTTP/2 多路复用做长期缓存;动态 import() 会自动拆包,别手动 require 把代码又揉回去。

⑤ 警惕插件成为瓶颈。 前面说过,插件钩子跨 napi 边界。一个在 transform 里正则狂扫整个文件、或做重复 JSON.parse/stringify 的插件,会在万级模块下被放大成秒级开销。优化插件本身(缓存结果、缩小处理范围)往往比调打包器参数更见效。

⑥ 缓存仍在演进。 Rolldown 当前最大的优势在冷启动和单发构建。类似 Vite 那种「依赖预打包缓存」的持久化机制仍在快速演进中,关注 rolldown-vite 的 cache 选项,别指望它今天就能像 Vite 那样把二次启动压到亚秒。

⑧ watch 与 HMR 的体感。 Rolldown 的增量构建仍在打磨;在 rolldown-vite 里,dev 的 HMR 由 Vite 运行时负责,Rolldown 只管「重新打包变更后的模块图」。当前体感已经接近原版 Vite,但超大型单体应用建议实测 HMR 边界延迟,别盲信基准里那串冷启动数字——冷启动快不代表每次保存都快。

⑦ 选型决策表(何时选谁):

你的场景推荐
极致库打包、要最严谨 tree-shakingRolldown(直接替代 Rollup)
重度依赖 webpack Loader / 生态rspack(Rust 版 webpack 兼容)
只要最快、不在乎 ESM 严谨度esbuild
已经是 Vite 用户rolldown-vite,零成本上车
需要 Rust 程序内嵌构建能力rolldown crate

6. 总结展望:JS 工具链的「Rust 化」终局

把镜头拉远,Rolldown 只是那条暗线上最新、也最关键的一环。前端工具链正在整体 Rust 化:

  • SWC 替代 Babel(转译)
  • esbuild / Rolldown 替代 Rollup / webpack 的 JS 部分(打包)
  • Biome 替代 ESLint + Prettier(lint + format)
  • Tailwind Oxide 替代原 JS 引擎(CSS 工具)
  • OXC 作为底层 parser/transform 被所有人复用

Babel / webpack / ESLint 的纯 JS 时代,正在系统性退场。这不是信仰之争,是性能账算不过来——当模块量级冲到万级、当冷启动被用户拿秒表量,JS 单线程那点吞吐确实顶不住了。

Vite 8 + Rolldown 的意义,远不止「构建快了几倍」。它把 dev 和 build 收敛到同一个引擎,意味着「dev 即 build」的体验真正闭合:你本地看到的模块图、依赖解析、分包策略,和生产产物是同一套逻辑。调试和产物的一致性,是过去十年前端构建最被低估、也最折磨人的质量指标。

对工程师来说,影响是双面的:

  • 好的一面:写插件仍是 JS(Rollup 兼容),你积累的 rollup 插件经验不过期;迁移成本极低,新项目直接 rolldown-vite 上车。
  • 需要适应的一面:底层心智从「JS 性能调优」转向「理解 Rust 工具链的输出契约」。你不需要会写 Rust,但你得知道为什么某个插件在 Rolldown 下变慢了、为什么 target 会影响产物。

迁移建议(务实版):

  1. 新项目直接试 rolldown-vite,把 import 来源一换即可。
  2. 老项目先评估插件兼容性——绝大多数 rollup 插件可用,留意极少数重度依赖 Rollup 内部 API 的插件。
  3. 先开 dev 灰度,再切 build,用一份产物体积 + 启动耗时的基准做回归对照。
  4. 生产关键链路锁版本,因为 Rolldown 仍在快速迭代,API 可能变动;把构建耗时和产物 hash 钉进 CI 监控。

顺便给个动手建议:别一上来就全量切。挑一个「构建慢、但插件少」的内部工具或组件库做试验田,跑通 rolldown-vite 后量一版耗时对比,把这份数据连同本文贴进技术周会,比任何安利都有说服力。工具链的升级从来不是单纯的技术判断,而是「用数据换共识」的过程——你越早拿出自己机器上的 1.6 秒,团队就越早下定决心。

最后一句收尾:前端的「构建」这件事,正在从一门需要专人调参的手艺,退化成一个你几乎感觉不到、却始终在背后毫秒级运转的基础设施。Rolldown 是这件事走向终局的关键一跃——它不炫技,它只是让「快」和「对」第一次不再需要我们二选一。

7. 真实迁移排障:dev/build 不一致的三种典型坑

理论说完,落地时你最该警惕的是「同一份代码,dev 和 build 表现不同」。统一到 Rolldown 后这类坑会大幅减少,但迁移期你得能认出它们。

坑一:CJS 依赖的具名导出互操作。 有些 npm 包是 CommonJS,却在被 import { foo } from 'legacy-pkg' 具名引入。esbuild 在 dev 预打包时会静态分析 CJS 的导出,遇到动态 module.exports 可能分析失败、把具名导入变成 undefined;Rollup 在 build 时用 __esModule 标记 + 运行时互操作层兜底。两种引擎的兜底策略不同,于是 dev 能跑、build 报 foo is not a function。换成 Rolldown 后,dev 和 build 用同一套 CJS 互操作逻辑,行为一致。

坑二:process.env.NODE_ENV 的 define 边界。 Vite dev 用 esbuild 的 defineprocess.env.NODE_ENV 替换成 "development";build 用 Rollup 的 @rollup/plugin-replace。两者对「变量未定义时是否报错」「字符串替换的贪婪程度」处理边界不同,偶尔出现 dev 下生效、build 下漏替换。Rolldown 内置 define,统一了一条替换规则。

坑三:动态 import() 的路径解析。 dev 下浏览器走原生 ESM,动态 import 的路径按浏览器规则解析;build 下 Rollup 会重写路径、做分包。某些依赖动态拼接路径的写法(如 import('./' + name + '.js'))在两种模式下命运不同。统一引擎后,解析规则只有一套。

认出这些坑的共同特征:凡是「dev 正常、build 炸」的诡异 bug,先怀疑 dev/build 双引擎差异,再怀疑自己代码。迁移到 Rolldown 后,这类怀疑可以直接划掉。

8. CI 基准矩阵:把三种链路放进同一脚本对比

别只信官方数字,把链路搬进你们自己的 CI,才算有底气。下面这个脚本把 rolldown、rollup+esbuild、rspack 跑同一份 19k 模块 bench,输出一张对比表:

// ci-bench.mjs
import { performance } from 'node:perf_hooks'
import { rolldown } from 'rolldown'
// 假设已各自安装 rollup / esbuild / @rspack/core 和同一份 bench/src

async function benchRolldown() {
  const t = performance.now()
  const b = await rolldown({ input: 'bench/src/main.js' })
  await b.write({ dir: 'out/rolldown', format: 'esm', minify: true, sourcemap: true })
  return (performance.now() - t) / 1000
}

async function benchRollupEsbuild() {
  const { rollup } = await import('rollup')
  const esbuild = (await import('rollup-plugin-esbuild')).default // 示意
  const t = performance.now()
  const b = await rollup({ input: 'bench/src/main.js', plugins: [esbuild()] })
  await b.write({ dir: 'out/rollup', format: 'esm', sourcemap: true })
  await b.close()
  return (performance.now() - t) / 1000
}

const rows = [
  ['Rolldown', await benchRolldown()],
  ['Rollup+esbuild', await benchRollupEsbuild()],
]
console.table(rows.map(([k, v]) => ({ 工具: k, 耗时_s: +v.toFixed(2) })))

跑在 CI 的固定规格机器上,连续取中位数,把结果钉进监控。哪天 Rolldown 升级后耗时异常,你能第一时间发现——而不是等用户投诉构建变慢。

9. 给团队的技术选型与落地清单

最后落到执行。一份可以直接贴进技术评审的清单:

  1. 新项目npm i -D rolldown-vite,把 vite 的 import 来源和 scripts 一换,开干。零迁移成本。
  2. 存量项目:先列一张「插件清单」,逐个核对是否在 Rolldown 的兼容范围内;绝大多数 rollup 插件可用,少数重度依赖 Rollup 内部 API 的需评估。
  3. 灰度顺序:先切 dev 跑一周,再切 build;用第 8 节的 CI 基准盯住耗时和产物体积。
  4. 锁版本:Rolldown 仍在快速迭代,生产链路务必锁 package-lock / pnpm-lock,升级走 PR 评审 + 基准回归。
  5. 监控:把构建耗时、产物 gzip 体积、产物 hash 钉进 CI;任一指标波动超阈值就拦截。
  6. 回滚预案:保留 viterolldown-vite 的双 package,出问题一条 scripts 改回即可,不阻塞业务发布。

Rolldown 不是银弹,但它把「快」和「对」第一次统一到了同一个引擎里。对还在 webpack/Rollup 里调参的前端团队来说,这是一次值得现在就排期的底座升级。


本文基准数据来自 rolldown.rs 官方 benchmark(19k 模块:10k React JSX + 9k iconify,开启 minify + sourcemap)。Rolldown 与 OXC、Vite 同属 VoidZero 生态。代码示例均可运行,Rust API 以官方文档为准。

推荐文章

2024年微信小程序开发价格概览
2024-11-19 06:40:52 +0800 CST
html一份退出酒场的告知书
2024-11-18 18:14:45 +0800 CST
使用 Git 制作升级包
2024-11-19 02:19:48 +0800 CST
Go语言中实现RSA加密与解密
2024-11-18 01:49:30 +0800 CST
mysql 优化指南
2024-11-18 21:01:24 +0800 CST
Web浏览器的定时器问题思考
2024-11-18 22:19:55 +0800 CST
程序员茄子在线接单