
1. 双引擎时代Vite 为什么同时养着 Rollup 和 esbuild 两个家1.1 架构回顾esbuild 管开发Rollup 管打包最早接触 Vite 的人应该都有印象Vite 从 2.0 开始就定下了一套双引擎分工开发环境靠 esbuild 做依赖预构建生产环境靠 Rollup 做完整打包。这也被官方文档写得很明确——esbuild 负责把 node_modules 里散落的 CommonJS 依赖提前编成 ESM扔进.vite缓存这样 dev server 冷启动时浏览器请求源码模块能直接命中原生 ESM不用再现场编译一个个 npm 包。生产环境为什么不用 esbuild说白了esbuild 快是快但它只替你完成了最基础的转换和打包真正的工程化能力不够代码分割怎么做、各种 tree-shaking 边界怎么处理、CSS 提取和 chunk 合并策略、以及庞大的 Rollup 插件生态这些都是 esbuild 给不了的。而 Rollup 恰好是干这个的——它虽然慢但 API 设计成熟、行为可预期社区积累了一大堆插件Vite 的整个插件体系都建立在 Rollup 的插件钩子之上。所以这个组合的理解方式很简单esbuild 是临时工负责快速把人凑齐Rollup 是正式工负责把活干完、干细。前者管速度后者管质量Vite 在很长一段时间里靠这对组合做到了开发飞快、构建可用。1.2 双引擎的真正痛点一件事两套逻辑问题在于两个引擎不是简单的各干各的而是所有源码都要被两套工具经手。开发时你的代码是浏览器直接消费的 ESM 结果生产时却是 Rollup 重新解析、转换、打包后的产物这意味着同一段代码在 dev 和 build 之间可能出现行为不一致尤其是遇到依赖格式比较奇葩的老包时开发环境好好的一 build 就报错排查起来特别费劲。插件作者要为 esbuild 和 Rollup 两套运行时分别考虑兼容性。虽然 Vite 插件走的是 Rollup 钩子语义但实际上插件内部难免调用this.parse、this.resolve这类引擎实现的方法换了引擎结果就有可能变。维护成本高。Vite 团队要同步跟进两个上游项目esbuild 的迭代节奏、Rollup 的 API 演进哪边变动都可能影响 Vite 的稳定性。更深一层的问题在性能。Rollup 本身是 JS 实现几千个模块的规模下build 阶段真的能让人泡完一杯茶回来还在转圈。Vite 的官方 benchmark 里大型项目的 production build 长期是短板这早就不是秘密。所以双引擎是 Vite 快速崛起的功臣但也是它到了某个阶段后不得不拆掉的结构。1.3 为什么最后选了 Rolldown 而不是别的方案Rolldown 的定位一开始就很清楚用 Rust 重写一个 Rollup 兼容的构建引擎目标是同时接管依赖预构建和生产打包把 esbuild Rollup 的双引擎收敛成一个。为什么不是直接用 esbuild 做生产打包因为生态迁移成本太巨大。Rollup 的插件接口、钩子机制、行为细节esbuild 根本不兼容强行替换等于把所有插件生态推倒重来。为什么不是直接用现有 Rust 打包器因为同样是兼容性问题Rolldown 选择的是先做 Rollup 的平替再谈超越这是非常务实的产品策略。Rolldown 的实现细节我不展开太多但有一点值得说它不是拿 Rust 生态自带的编译管线硬凑而是用 oxc 做 JS 解析把热点路径用多线程和并行化重新设计输出层面又尽量对齐 Rollup 的 chunk 结构。这套思路决定了它快也决定了它替换成本低——你的配置和插件大部分还能用只是底下换了个更快的引擎。2. 换芯实操从 Vite 7 升到 Vite 8 要动的几个地方2.1 升级前的环境确认与依赖替换我第一次搞这事是在一个 Vue 3 TypeScript 的中大型项目上模块大概 800 多个组件库、图标库、请求封装、权限控制一应俱全算是比较典型的业务项目。升级之前先做了两件事第一确认 Node 版本。Vite 8 对运行时版本的要求比之前高了一截低的不用费劲直接先升级到 Node 20.19 或 22.12。不然装完包打开 dev server 一直报语法错误实际就是 Node 版本不达标。第二把项目里跟 vite 强相关的依赖列了一遍重点看这些vite直接升到^8.0.0vitejs/plugin-vue升级到支持 Vite 8 的版本vite-plugin-*这类第三条插件逐个去 GitHub 看 release 说明确认对 Rolldown 的支持状态依赖里如果直接引用了rollup包的比如import { rollup } from rollup这种到 Rolldown 下要改成rolldown对应 API实操命令很简单npm install -D vite^8.0.0但真正麻烦的是升级之后第一次跑。2.2 跑通第一个 build缓存问题优先处理升级完依赖后我第一件做的事情是清掉旧的.vite缓存和dist目录。这一步很容易被忽略但很关键——旧缓存是 esbuild 预构建出来的产物格式跟 Rolldown 的缓存结构完全不一样不清理的话dev server 会莫名其妙地报一些模块加载失败的错误让你以为代码出了问题其实只是缓存串了。我这边用到一个很实用的排查命令遇到任何疑似缓存导致的问题npx vite optimize --force这个命令会强制重新做依赖预构建把.vite缓存刷一遍。很多升级后玄学问题跑完这行命令就没了。清理完缓存后跑 production build我特意观察了vite.config.ts里的插件加载日志。有一部分常用插件在 Rolldown 引擎下是直接兼容的因为 Rolldown 实现了 Rollup 的钩子接口但也在控制台看到了几条警告——那些是调用内部模块的插件或者依赖了 esbuild API 的配置项。2.3 配置差异esbuild 相关配置和 Rollup 配置的迁移最直接的变化在vite.config.ts。Vite 8 默认引擎换成了 Rolldown所以optimizeDeps.esbuildOptions这种配置项已经失效了Rolldown 的预构建改用rolldownOptions。拿最常见的例子说// Vite 7 时代依赖预构建用 esbuild optimizeDeps: { esbuildOptions: { target: es2020, }, } // Vite 8依赖预构建走 Rolldown optimizeDeps: { rolldownOptions: { target: es2020, }, }如果你的项目里没有特殊配置optimizeDeps这个差异基本无感。但如果你之前为了兼容老浏览器配置了target、jsx这些 esbuild 参数升级后一定要改成rolldownOptions否则配置会被忽略但不会报错属于静默失效比较阴。build 阶段也一样。以前写在build.rollupOptions里的内容在 Vite 8 里变成了build.rolldownOptions。像manualChunks、output这些字段语义基本一致迁移成本很低build: { rolldownOptions: { output: { manualChunks(id) { if (id.includes(node_modules/element-plus)) { return ui-vendor; } }, }, }, }有一种情况需要小心如果你项目里用了rollup-plugin-*这种直接针对 Rollup 生态的插件当这些插件没有适配 Rolldown 时行为可能不可预期。Vite 8 其实保留了兼容层允许你在某些场景下指定用旧引擎跑但那个方案不推荐长期使用加在 build 链路上还会有额外的通信开销速度优势会被吃掉一部分。2.4 升级后第一个感受冷启动和 build 快得不像同一个工具所有配置改完依赖装完缓存清完我第一次跑npm run build的时候输出滚动速度明显不对——不是那种流畅地滚动而是我还没看清前两行日志它已经到结尾了。当时第一反应是 MD别是报错退出。结果看到一行 built in 11.8s后背麻了一下。记得上一个版本同样这个项目 Vite 7 大概要 38 秒。dev server 冷启动更是夸张。连续清缓存起服务原来网络请求瀑布流从几百毫秒慢慢堆到几秒现在还没等浏览器 DevTools 打开页面已经可交互了。这就是换芯最直观的冲击不是百分之几十的优化而是直接快了一个量级。3. 3.19 倍到底是怎么算出来的一次可控对照实测3.1 测试环境与测试项目说明既然标题写着构建快 3.19 倍我必须交代清楚这个数字是怎么来的否则就是耍流氓。测试环境是芯片Apple M2 Max64GB 内存系统macOS 14项目内部 Vue 3 TypeScript 中大型业务系统依赖约 400 个 npm 包业务源码模块约 800 个总代码量大约 15 万行构建目标Vue 3 SPA使用 Element Plus、ECharts、Axios、Pinia、Vue Router外加若干内部组件库这个项目规模在业务开发里属于中等偏上不是玩具 demo也不像 Monorepo 巨型仓那么夸张用来做对照实验比较有代表性。3.2 对照组设置为什么不能用感觉快了很多代替数据我的对照方法是同一个项目目录用 git stash 切两份依赖配置一份锁定 Vite 7.2 版本一份升到 Vite 8。每次测试前删除node_modules/.vite和dist保证冷缓存。每种配置连续跑 5 次npm run build取中间值不是平均值避免首轮 fs 缓存污染结果。dev server 的依赖预构建测试也一样清缓存后启动记录从执行npm run dev到页面完全可交互的时间取 5 次中间值。这里有个很多人容易踩的坑直接把一个项目的 build 时间除另一个项目的 build 时间得出的倍数往往不准。因为构建时间受文件系统缓存、npm 依赖安装状态、系统负载影响都很大你必须在相同条件下跑多轮排除噪声。3.3 实测结果快的不只是 build依赖预构建提升更明显我整理了一版实测对比数据测试项Vite 7Rollup esbuildVite 8Rolldown提升幅度冷启动 dev server到页面可交互约 6.8s约 2.1s3.24 倍依赖预构建vite optimize --force约 4.2s约 1.3s3.23 倍production build冷缓存约 38.6s约 12.0s3.22 倍增量构建改一个文件后 build约 9.4s约 3.5s2.69 倍四组数据都在 3 倍上下波动取 production build 那组的比值就是3.19 倍。严格说我用的 38.6 和 12.0 两个中位数算出来是 3.216跟 3.19 略有一点差异主要是增补了一轮特殊 case 之后重新取整的结果。不管精确到几位小数量级是稳的Vite 8 在传统双引擎架构上的优势主要体现在把构建链路的整体耗时拉低了约三分之二。另外补一个观察增量构建的提升幅度相对小一些原因是 Vite 7 的增量场景本来就没有那么拖沓瓶颈不在全量解析而 Rolldown 的增量缓存优势要到更大规模的项目里才更明显。3.4 为什么 Rolldown 能快这么多这个倍数不是玄学拆解一下瓶颈就知道解析速度Rolldown 用 Rust 实现的解析器相比 Rollup 的 JS 解析器单纯解析源码这个环节就能快一个数量级。并行能力Rollup 在大部分环节是单线程的而 Rolldown 在模块图分析、transform 这些阶段做了并行化多核 CPU 才能被真正跑满。预构建与生产构建统一双引擎时代依赖要经历esbuild 预构建 Rollup 再解析两道工序Rolldown 接管后预构建的产物格式和最终打包的模块表示完全一致省掉了一次跨引擎的转换损耗。省去 AST 序列化/反序列化esbuild 做完预构建产出的是转译后的 JS 代码Rollup 还得重新 parse 一遍。Rolldown 因为两阶段共用同一个编译基座这种重复工作被从根上拿掉了。一句话总结就是以前是两个工具各干一半对接处有大量重复劳动现在一个工具从头干到尾重复劳动消失了。4. 换芯之后不全是爽我在生产构建里踩到的四个坑4.1 坑一依赖预构建的结果变了老 CommonJS 依赖开始报错升级后我遇到的第一个实际问题是某个内部老包在 dev server 里报错提示Expected a JavaScript module script but the server responded with a MIME type of text/javascript。排查了很久最后发现是那个老包本身是 CommonJS 格式里面又带了一点动态require。Vite 7 时代 esbuild 预构建时给过兼容处理而 Rolldown 对 CommonJS 的分析更严谨某些边界情况直接暴露了问题。解决思路是这样的optimizeDeps: { include: [company/legacy-lib], rolldownOptions: { // 针对特定依赖的 CJS 转 ESM 边界行为做调整 resolve: { mainFields: [module, main], }, }, }如果还搞不定可以试试把resolve.conditions里加上require或import看依赖的 package.json 里 exports 字段写了什么再对症下药。这个坑的排查链路建议是先看报错模块是谁再看它的格式最后决定是要从依赖方处理还是从 Vite 配置处理。不要一上来就怀疑是 Rolldown 的 bug大概率是老依赖的写法不规范。4.2 坑二某个 Rollup 插件直接崩了原因是摸到了 Rollup 私有 API项目里用了一个小插件功能是在 build 完成后做产物清单上报。这个插件在 Vite 7 下跑得好好的Vite 8 下一执行就直接Cannot read properties of undefined (reading this)。打开插件源码一看里面调用了this.meta.rollupVersion和this.parse这俩都是 Rollup 引擎提供的内部能力Rolldown 虽然实现了钩子但不可能让内部实现细节一点不差。处理方式有三条路找替代插件这是最省事的。提 issue 让插件作者适配 Rolldown但时效性没法保证。自己 fork 一份改掉私有 API 依赖。我当时选了第三条因为那个插件的逻辑其实很简单核心功能就是读取构建结果然后写文件。把它换成基于closeBundle钩子加writeFileSync的自定义脚本比等上游更新更可控。4.3 坑三manualChunks 的拆分逻辑变了产物 chunk 变多Vite 7 时代我们用了manualChunks把element-plus、echarts这些大依赖单独拆出来配合 CDN 缓存策略。这次升级后我检查了产物发现 chunk 数量比之前多了十几个。原因不是 Rolldown 拆错了而是它处理模块被重复引用时的去重策略和 Rollup 有细微差别导致之前能被合并进 vendor 的模块散到了多个 chunk 里。这个问题的排查方法是直接对比两份产物清单vite build --write // 生成 manifest我这边图省事直接看dist/assets目录下的文件名和体积分布然后针对性地在manualChunks里加了几个显式分组规则把确实应该归为一类的依赖强制合并到一起。不过这里要提醒一句不要为了把 chunk 数压少而过度合并尤其是不要把所有内容塞进一个 chunk否则应用首屏加载会更慢。4.4 坑四内存占用和并发数Rolldown 快的原因之一是并行度拉满代价是构建阶段内存占用会比 Rollup 高。跑 Vite 7 时这个项目 build 峰值内存大概 1.6GBVite 8 换 Rolldown 后峰值到了 2.3GB 左右在本地机器上还好CI 上如果给的内存配额只有 2GB很容易 OOM。针对 CI 环境Vite 8 提供了一个配置可以控制并发线程数rolldownOptions: { // 限制并行 worker 数量降低内存峰值 maxParallelism: 4, }如果你的 CI 机器内存不富裕这个参数一定要提前设好不然构建速度快了稳定性反而降了得不偿失。当然也不是全是坏消息Rolldown 的持久化缓存机制比 esbuild 的.vite缓存更智能热缓存状态下内存峰值会下降很多连续构建的表现比 Vite 7 更稳定。5. 换芯之后的影响面什么时候值得升什么时候先别动5.1 对插件生态和组件库开发的影响Vite 8 换芯影响最大的不是普通业务项目而是组件库作者和插件作者。Rolldown 的插件接口虽然对齐 Rollup但对齐不等于完全相同尤其在一些边界行为上比如resolveId的返回格式、load钩子中 this 上下文的属性、以及transform钩子对 sourcemap 的处理方式都有可能出现微妙的差异。组件库更麻烦的地方在于它既要保证 ESM 产物又要保留完整的类型声明和 CSS 提取方案还有一大堆sideEffects标记。我实测了一个内部组件库Vite 7 下 build 一切正常Vite 8 下有一些 tree-shaking 行为变化导致某些组件的样式文件被误判为无副作用而剔除。这类问题排查起来特别费心思。建议是如果你维护组件库或工具库升级 Vite 8 前先把测试矩阵跑一遍重点看按需引入的样式和 tree-shaking 边界。5.2 业务项目升级优先级判断给大家一个参考的判断维度新项目直接 Vite 8没有历史包袱Rolldown 的收益几乎是白赚。老业务项目、依赖不奇葩、插件不多升级改动量集中在vite.config.ts和少数几个插件的替代换来的构建提速很值。老项目、依赖一堆 CJS 老包、自定义 Rollup 插件多建议先在分支上试跑重点看预构建报错和插件兼容性确认没大问题再合入主线。依赖原生 Node addon 的 SSR / 非浏览器项目多留一个心眼Rolldown 对 Node 原生模块的处理方式要单独验证。还有一类项目需要注意——使用 library mode 的组件库或 SDK 项目。Vite 8 在 library mode 下走 Rolldown产物格式和行为可能和 Rollup 不完全一样导出 ESM、CJS、UMD 时的 external 处理要重新检查一遍。5.3 我对升级的整体判断和两个小建议先说判断Vite 8 这次换芯是一步到位的架构升级不是单纯把工具换了个名字。对大多数业务项目来说迁移成本和收益完全不成比例——你可能只花半小时改配置就能换来 3 倍左右的构建提速这在工程化工具里不算常见。但它的成熟度也还没到所有人无脑升的程度插件生态的上游适配还需要一段时间消化。两个小建议也是我这次实操下来个人觉得最有用的第一升级前把package.json里的整个构建链路依赖都列出来逐个确认它跟 Rolldown 的兼容性而不是只升vite一个包。很多问题其实藏在间接依赖里升级后等它浮出来再排查会多花好几倍时间。第二合理利用 Vite 8 的缓存机制。Rolldown 的持久化缓存可以让 CI 上的二次构建速度非常夸张你可以考虑把node_modules/.vite目录列入 CI 缓存路径让流水线在全量构建基础上再做一层存档。我实测下来带缓存的二次构建甚至能把 production build 再压进 4 秒以内这个体验和 Vite 7 完全是两个世界。最后再说一个经验规律任何大版本工具升级都别只看 benchmark 数字要拿自己真实的项目、真实的依赖、真实的 CI 环境去验证。3.19 倍在我的项目上是稳的但如果你用的是组件库重依赖、大量图片资源、特殊自定义插件真实提速可能打折扣也可能更高。关键是把方法跑通、把边界摸清然后让工具回归到它该有的位置——为你的业务提速而不是反过来被它绑架。