最近社区里那几个性能数字确实把我看愣住了。一个用 Rust 重写前端编译与格式化链路的项目直接亮出数据编译快了 27 倍格式化快了 3666 倍尤雨溪还在转发里给了相当高的评价。用 Vue 写前端这么多年从 vue-cli 一路等到 Vite我对“编译期间先喝口水”早就习以为常。可当看到这两个数字时我第一反应是Vue 工具链是不是真的要改朝换代了这篇文章不打算复述新闻我想从一个实际搞过 Vue 项目、也折腾过工具链的人的角度把这件事拆开讲清楚性能数字是从哪来的Rust 化 Vue 工具链目前有哪几条路线现在能不能搬到自己的项目里以及迁移过程中我踩过的坑。全程没有玄学都是能落地的经验。1. 27 倍和 3666 倍背后JS 工具链到底慢在哪1.1 一条 Vue 代码从源码到浏览器的完整链路先说清楚一个 Vue 项目构建时到底发生了什么。很多人对“编译”的理解就是一条命令的事其实从你写完一段.vue文件到浏览器里能跑至少要经过这几道工序依赖预构建把 node_modules 里的 ESM 包转成浏览器可用的格式同时合并重复依赖SFC 编译把template编译成 render 函数把script里的 TS 转成 JS把style scoped加上作用域标记转译与降级处理 JSX 也好、TS 也好、新语法也好都在这层做 AST 级别的转换Tree-shaking 与打包分析模块依赖生成最终的 chunk 文件压缩与格式化把产物做 minify去掉死代码代码格式化开发过程中保存文件时整理代码。这里面每一道工序都涉及词法分析、语法分析、AST 遍历、代码生成这几步。你写一个v-if指令编译器要先把它读进内存、拆成节点、判断条件、再拼成一段 JavaScript 代码。Vite 虽然已经用 esbuild 解决了一部分依赖预构建的慢但 SFC 编译器vue/compiler-sfc和底层大量插件仍然跑在 JavaScript 运行时上插件链里的 Babel、TS 转译、Terser本质上都是 CPU 密集型的解析加遍历任务。问题就在这里这些任务本身极其吃 CPU但它们的实现语言是 JavaScript。JS 是为快速启动、事件驱动场景设计的语言不是为长跑式解析大型代码库设计的。一个几十万行的项目Babel 解析成一个 AST 就可能要几百毫秒甚至几秒后面每一次插件转换又是新的一轮遍历。于是你看见的现象就是Vue 项目小的时候一切流畅一旦模块数量过千、依赖层级变深保存一次代码后的 HMR 更新就会从几百毫秒膨胀到几秒生产构建更是能跑到几分钟。这还没有算上格式化——Prettier 在处理一个几千行的文件时节点递归加字符串重建的开销经常让人等到怀疑编辑器卡死。1.2 Rust 凭什么把这段路程缩短一个数量级Rust 能快其实不是魔法而是几个很直接的原因叠加。第一它编译出来的是原生机器码。没有解释器不需要 JIT 预热。JS 工具链跑起来前几百毫秒可能都在“热身”V8 得把热点代码编译成机器码之后速度才上来。Rust 的边框就是一批已经编译好的二进制进程一启动就是满血状态。你别小看这一点前端工具链大多是短生命周期进程跑一下就退出冷启动开销占比极高。这也是为什么很多 benchmark 里JS 工具和 Rust 工具的差距在最冷的第一次运行上最惊人。第二内存布局和遍历效率完全不同。Rust 的 AST 节点是紧凑的、类型明确的结构体而 JS 的 AST 是松散的、带大量动态属性的对象。同样遍历一棵树Rust 直接沿着内存顺序读下去JS 每次要处理属性查找、原型链、GC 压力。这就像一个是提前排好索引的仓库一个是把所有物料堆在纸箱里再挨个拆箱找东西。第三Rust 可以轻易做并行处理。JS 工具链受限于单线程模型进程内并行很麻烦要么开 worker 线程要么拆子进程通信成本还不小。Rust 标准库直接支持多线程文件级别的解析、转换任务天然可以分片交给多个核心并行处理。在现在的多核机器上这等于白捡了好几倍吞吐。第四代码生成阶段也没有浪费。Rust 能直接写高度优化的字符串输出逻辑还能用 arena 分配器集中管理临时内存做到几乎无碎片。而 JS 工具在生成大量中间字符串时会频繁触发 GC越大的项目 GC 停顿越频繁速度就肉眼可见地往下掉。1.3 别被大数字吓到这些 benchmark 是怎么测的看到“格式化快 3666 倍”这种数字很多人第一反应是怀疑这很正常我一开始也不信。但拆开 benchmark 的方法看这个量级是有道理的。格式化任务典型测法是对同一个大型文件反复格式化若干次。Prettier 每次运行都要经历加载插件、解析文件成 AST、遍历节点、重建字符串、再和原文件做 diff。加载插件和 AST 重建本身就很重一次几百毫秒很正常。Rust formatter 则是直接解析、直接格式化输出中间少了无数层抽象一次可能只要零点几毫秒。再叠加前面说的 JIT 预热差异3666 倍这种极端数字完全可以出现——前提是文件足够大、样本足够冷启动。编译快 27 倍同理通常是在一个包含几千个模块的工程上比较完整跑一遍vite build的时间。Rollup 的打包逻辑是精确但啰嗦的每做一个 tree-shaking 分析都要完整遍历模块图Rust 版打包器在同样的模块图上用并行遍历和位掩码标记省掉的不是一点半点。但我也要给你提个醒这些 benchmark 数字有场景性。如果你的项目只有 5 个文件那 Rust 工具链和 JS 工具链的差距可能只有两三倍因为启动时间占比没那么夸张。真正拉开差距的是那些模块多、文件大、历史包袱重的工程。换句话说这些数字说明的不是“所有项目都能快这么多”而是“工具链的性能天花板被捅穿了”。2. 当前 Rust 化 Vue 工具链的三条路线标题问“要革 Vue 工具链的命”那到底是怎么革的现在市面上其实有三条明显不同的路线我建议你先把它们区分开不然很容易被各种项目名搞混。2.1 Oxc一个野心很大的编译器全家桶Oxc 的全名是 Oxidation Compiler它想做的事情非常大用 Rust 重新实现 JavaScript/TypeScript 生态里所有跟解析、转换、检查相关的核心工具。它包含了 parser、linter、formatter、transformer、minifier 等一整套组件往上还能被别的工具链当底层库用。它的核心价值在于“一个项目服务所有上层工具”。以前 Babel 要解析一遍、ESLint 要解析一遍、Prettier 要解析一遍每个工具都独立维护一棵 AST。Oxc 的目标是统一解析一次让 lint、format、transform 都在这份结果上操作省掉重复开销。这个思路对 Vue 开发者意义很大因为.vue文件里有 template、script、style 三段代码多工具各自解析的浪费比纯 JS 文件还要严重。Oxc 的 parser 已经相当成熟而且在公告里反复强调兼容 Babel 和 TypeScript 的行为。我对它的评价是它是这一轮 Rust 工具链浪潮的“地基”未来你用的框架工具可能底层都踩着 Oxc 的解析器。2.2 RolldownVite 内核的“下一个版本”如果说 Oxc 是地基那么 Rolldown 就是直接冲着 Vue 开发者日常使用的 Vite 来的。Vite 开发模式下靠 esbuild 转译模块但在生产构建时还是要落到 Rollup 上做打包和 tree-shaking。Rollup 的插件生态和产物质量都很成熟但它是 JavaScript 写的跑大型项目时就是慢。Rolldown 做的事情就是用 Rust 重新实现一个与 Rollup 插件接口兼容的打包器目标是在不改变 Vite 使用习惯的前提下把打包内核整个换掉。尤雨溪转发和评价的方向也明显是奔着这个去的。他反复表达过的观点我一直记着工具链本身不能成为开发者的敌人。一个项目如果每次构建都要等两分钟团队就不可能把构建时间节约下来的精力投入到有创造性的开发里去。而 Rolldown 走了一条很聪明的捷径它先兼容 Rollup 的插件接口让你现有的 vite-plugin、rollup-plugin 大部分还能用迁移的时候代码改动很小。这一点很关键。工具链换代最大的成本从来不是性能是生态迁移。如果 Rolldown 不兼容 Rollup 插件哪怕它快一百倍现实世界也没人敢迁移。它现在走的就是“先兼容、再优化、最后超越”的路子。2.3 Rspack 和其他打包方案换个思路的备选第三条线是 Rspack。它是字节跳动开源的 Rust 打包器兼容的是 Webpack 的配置与插件体系而不是 Rollup。如果你的项目是从 vue-cli 时代留下来的老 Webpack 工程Rspack 是比 Rolldown 更对口的迁移目标因为本来就是用 Webpack 那套 plugin/loader 思维组织代码的。另外还有一个不能忽略的项目是 Biome前身是 Rome。它主打的是用 Rust 替代 ESLint Prettier 这一整条“检查和格式化”链路。它和 Oxc 的 formatter 存在竞争关系但从目前社区活跃度和工具完整性看Biome 已经进到了很多团队的实际工作流里下一篇我准备单独写它这里先不展开了。2.4 表格对比三个项目怎么选项目主要替代对象与 Vue 生态的亲密度成熟度适合场景OxcBabel / ESLint / Prettier 的底层解析与转换高未来 Vue 编译器可能底层受益部分组件可用仍在快速迭代想参与工具链开发、需要高性能底层解析RolldownRollup最终会替换 Vite 生产构建内核极高Vite 的下一个重大升级方向实验性官方不建议生产直接用想抢先体验 Vite 下一代性能、愿意做验证分支RspackWebpack中配合 Vue 组件库 loader 可用相对成熟已有生产实践老 Webpack 工程想降低构建耗时我现在的判断是如果你用的是 Vite未来两年内最该盯紧的是 Rolldown如果你还在 Webpack 里挣扎Rspack 可以立刻解决痛而 Oxc 更像一个基础层普通应用开发者暂时不需要直接调用它但你用的工具会悄悄换成它的内核。3. 把 Vue 项目切到 Rust 工具链的完整实操讲完原理和路线下面说点能直接上手的。我以“迁移一个 Vite Vue 3 项目”为例走一遍从评估到切换的完整过程。3.1 评估你项目的可迁移性动手之前先别急着装依赖先花十分钟给项目做个体检看它适不适合现在切。体检清单是这么几条项目里有没有大量自定义 Rollup 插件如果只是用了官方vitejs/plugin-vue、vitejs/plugin-legacy这类基础插件迁移风险很低。如果有自己写的renderChunk、generateBundlehook或者依赖this.getModuleInfo()这类内部 API 的插件就要警惕。有没有重度依赖 Webpack loader 写法的历史代码Vite 项目一般没有但如果是 vue-cli 迁移过来的可能残留url-loader、file-loader时代的处理方式。生产构建里有没有自己搭的复杂 multi-page 配置、动态import()路径解析、特殊的 chunk 拆分策略这些在打包器更换后最容易出差异。项目规模多大少于 500 个模块的小项目切不切体验差距不明显超过 3000 个模块、构建超过 60 秒的项目收益最明显。如果以上都过关就可以进入下一步。3.2 用 Rolldown Vite 替换现有 Vite现在的方式不是重写项目而是加装一个实验性的 Rolldown 版 Vite。我建议你单独开一个分支来做不要在主干直接动。第一步安装依赖npm i -D rolldown-vite # 或者 pnpm add -D rolldown-vite第二步改启动脚本。以package.json为例{ scripts: { dev: rolldown-vite, build: rolldown-vite build, preview: vite preview } }第三步清掉旧的 node_modules 缓存和依赖预构建缓存避免它读取到旧的 Vite 缓存数据rm -rf node_modules/.vite rm -rf node_modules/.rolldown-cache第四步跑npm run dev。如果项目插件兼容性没问题你应该能直接打开本地页面。第一次启动时 Rolldown 会做依赖预构建但因为底层是 Rust速度会明显比旧版快。这里要特别强调rolldown-vite 目前在官方文档里仍然是实验状态不适合直接推到生产环境。我自己的做法是开一个feat/rolldown-experiment分支白天开发用传统 Vite中午找个空档跑 Rolldown 验证分支看构建速度和产物是否有问题。等验证得差不多了再把实验结论同步给团队。3.3 格式化链路替换从 Prettier 到 Rust formatter这一块比打包内核亲民得多风险也低得多。我现在的建议是格式化工具可以立刻切不需要观望。我以 Biome 为例因为它目前是把“格式化 lint”打包做得最完整的 Rust 工具而且对 Prettier 的兼容迁移做得不错。操作如下首先安装并初始化npm i -D biomejs/biome npx biomejs/biome init初始化之后会生成biome.json里面默认是关闭了 formatter 的要手动打开{ formatter: { enabled: true, indentStyle: space, indentWidth: 2, lineWidth: 100 }, javascript: { formatter: { quoteStyle: single, trailingCommas: all } } }如果你项目里原来有.prettierrc可以试着用 Biome 的迁移命令把配置搬过来npx biomejs/biome migrate prettier它会尽量把 Prettier 的配置项翻译成 Biome 的配置。注意不是 100% 相等迁移完一定要自己检查一遍biome.json。然后跑一次全量格式化npx biomejs/biome format --write .这一步执行完之后git diff会很大因为 Rspack 也好、Prettier 也好、Biome 也好任何格式化工具的代码风格都有细微差异。要让团队接受这次切换最关键的一步是“一次性锁基线”全量格式化提交一次之后强制所有新代码用 Biome 格式化。如果只改了一部分文件后续每次合并都会产生风格冲突那才是灾难。我在 VSCode 里是这样接的装好 Biome 扩展把默认 formatter 改为 Biome保存时自动格式化。注意要把.vscode/settings.json里原来的 Prettier 配置清干净不然编辑器会同时调用两个 formatter互相打架。3.4 建议的迁移顺序与回退方案我给团队定的迁移顺序是这样的你参考一下第一阶段格式化独立切到 Rust 工具。风险最低收益立竿见影团队每天都用感知最强。先做。第二阶段Rolldown 进入验证分支只跑开发服务器不参与生产构建。这个阶段主要看插件兼容性和 HMR 稳定性。第三阶段用 Rolldown 跑生产构建与旧构建产物做对比。核心指标是 chunk 体积变化、资源路径是否正确、SSR 场景下有没有问题。第四阶段全量铺开删除旧 Vite 依赖。每一阶段都要留一条回退路径。比较简单的做法是保留package-lock.json或pnpm-lock.yaml的上一版本出了问题几条命令就能回到原状态。我不建议在没有任何预案的情况下直接替换默认构建命令因为工具链再怎么新保证团队能按时发布才是第一优先级。4. 迁移路上的真实坑位清单下面这部分是我实际折腾出来的经验。你没真跑过很多坑根本意识不到。4.1 插件兼容看起来能跑跑起来报错Rolldown 对外宣称兼容 Rollup 插件但“兼容”是有等级的。只用了transform和load钩子做纯内容转换的插件基本没压力但凡是用了renderChunk、generateBundle这类“在产物生成阶段动手脚”的插件很容易出问题。因为 Rust 版打包器的产物生成流程和 Rollup 并不完全一致插件里如果假设了“chunk 文件一定以这样的路径生成”“变量名一定长这样”就会拿到预期外的数据。我踩到的一个典型案例是一个自动注入网络请求监控脚本的插件在generateBundle里遍历 chunk然后把脚本代码拼到每个 HTML 入口里。在 Rollup 下一切正常到了 Rolldown 下 chunk 文件名变了插件直接拿到空列表监控代码静默消失。这个 bug 没有报错只有上线后数据对不上才发现。所以迁移后一定要在小范围测试环境跑一遍完整功能链路特别是那些依赖构建期注入脚本的统计、监控、灰度逻辑。4.2 产物不一致两个打包器差异排查即便所有插件都正常工作Rolldown 生成的产物也不会和 Rollup 完全一样。最常出现的差异有三个tree-shaking 的结果不同、CSS 提取顺序不同、动态 chunk 的命名策略不同。tree-shaking 不同听起来像坏事其实不完全是。Rust 版打包器对“模块副作用”的判断可能比 Rollup 更激进会把一些原本 Rollup 因为保守而保留的“看起来没用到但有副作用”的代码直接删掉。这能带来更小的体积但如果你在某个模块里偷偷执行了全局事件绑定而且没被编译器识别出副作用那这些代码就会消失。排查方法我建议这样做用两个打包器分别构建一次把产物的总大小、每个 chunk 的大小、文件数量拉一张表对比。如果差异小于 5%基本不用担心如果某个 chunk 的差异很大单独去翻那个模块的代码看是不是存在“依赖执行顺序”的隐式逻辑。4.3 HMR 体验变化不只是快HMR 变快是肯定的但 Rolldown 的 HMR 实现和旧 Vite 有一些行为差异。最典型的是循环依赖。有些项目用循环 import 完成模块间的状态共享这在旧工具里能“勉强跑起来”新工具改走更严格的依赖追踪后更新时可能出现变量 undefined。遇到诡异的热更新问题第一件事不是改代码而是清缓存。Rolldown 的预构建缓存目录和 Vite 不同在node_modules/.rolldown-cache下。删掉这个目录再重启开发服务器很多“改了一个文件、整个页面白屏”的问题都能解决。4.4 格式化回归统一风格要先付一次“迁移成本”格式化工具切换最大的坑不是速度是风格基线迁移。你的项目如果是多年沉淀历史代码的格式化风格可能存在大量“历史遗留”有人手写了特殊括号换行、有人特意把长 JSX 手动折行、有人用 Prettier 2 和 Prettier 3 混着格式化过。Biome 的默认行为不一定和这些手写风格吻合所以第一次全量格式化后diff 里会出现一些“看起来不如原来好看”的改动。这是正常现象别慌。你要做的是明确告诉团队先接受这一次全量 diff把它当作风格基线的统一节点。切换后的前一两周会有少量代码风格在 code review 里反复出现但挺过这阵子收益就出来了。我个人建议挑一个大版本发版的空窗期做这件事不要在功能迭代最密集的时候掺和。另外Biome 里某些规则和 Prettier 的 behavior 存在有意或者无意的不同。比如 Prettier 默认会给某些复杂表达式加括号保持可读性Biome 可能更激进地删除“非必要括号”。我遇到过团队里有人写了一段很复杂的位运算Prettier 留了括号Biome 直接给他精简成一行可读性下降不少。遇到这种情况直接通过 lint 规则强制保留括号就行Biome 的noExtraParentheses这类规则都可以按团队偏好调整。5. 性能提升之后工作流反而变了工具链提速到一定程度影响的不只是构建时长它还会反过来改变开发者的工作习惯。这一节我想聊聊那些“数字之外”的变化。5.1 全量检查常态化从“不敢跑”到“每次提交都跑”以前项目大了之后很多团队会刻意避免全量 lint 和全量格式化因为跑一次几分钟ci 时间扛不住。于是我们只对git diff的文件做检查这导致的后果是很多历史代码的 lint 问题长期无人问津技术债越堆越高。工具链快起来之后我在一个中型项目上试过把 pre-commit 从增量检查改成全量检查。开始我还担心会不会拖慢提交流程实测下来整个预提交阶段加起来的耗时和原来差不多甚至更快。因为 Rust 工具在格式化、lint 上的吞吐实在太猛三千个文件检查一遍只需要几百毫秒。这个改变带来的是“白衬衫效应”当全量检查的成本低到可以忽略团队就不再有借口把旧债留到“以后再说”。每次提交都会顺手把全量问题暴露出来技术债的增长速度被明显遏制住了。5.2 对团队工程化的倒逼工具链快了以后你会发现以前靠“增量编译”掩盖的低效结构问题会浮出来。比如某个模块被三百个文件同时 import以前构建慢的时候没人注意重建一次三秒钟大家也就忍了现在快是快了一旦有同事重构这个模块需要重新构建的依赖图还是那么大新建的 chunk 分析结果一眼就能看出问题。所以我会建议团队在切换工具链的同时顺手做一轮依赖治理看看有哪些模块在项目里被过度共享了、哪些深层 import 路径可以收敛、哪些 re-export 只是徒增解析成本。这个动作在过去“构建两分钟”的项目里做起来很痛苦因为你每改一处都要等一次构建验证现在构建只要几秒验证成本变得可以接受治理起来顺手多了。5.3 关于 Rust前端要不要学聊到这很多人会问那我是不是得去学 Rust 了我的回答是分两层。对于绝大多数 Vue 应用开发者来说现阶段你不需要手写 Rust。你需要做的是看懂工具链的变化趋势、会跑 benchmark 验证、能排查插件兼容问题。工具链换代对于普通开发者来说体验应该是“无感变快”而不是“逼我学新语言”。但如果你是做工程化方向、或者对工具链本身着迷的开发者现在是一个挺好的入口。我个人的建议是别从语法书硬啃起直接去读 Oxc 或 Rolldown 的源码测试文件看它怎么断言解析器行为、怎么处理边界用例。Rust 的所有权模型在第一周肯定会让你别扭但配合着真实代码读反而比抽象学语法要快得多。前端这个圈子里拿着 Rust 重写工具链的趋势还会持续好几年早一步熟悉未来可迁移的技能点就多一些。我在自己一个中型项目上做替换实验时记忆最深的不是那个 27 倍的数字而是第一次跑完 Rolldown 构建时那种“日志都还没来得及刷屏dist 已经出来了”的错愕感。格式化切换更是当即见效原来保存一个超大型组件要等上两秒现在手指离开键盘的瞬间就已经完成了。就我现在的使用体验来说立刻值得做的是把格式化链条切到 Rust 工具打包内核可以再养一养插件生态。等 Rolldown 把生产环境的兼容性打磨到位我一定会第一时间把正式项目切过去——那时候再回头看这些 27 倍、3666 倍的数字可能就只是对过去时代的一个纪念了。
阅读完成 · 觉得有帮助?