
Vite 8.0 发布之后我第一件事不是逛 Release Notes而是赶紧拿了一个 Vite 7 的老项目试升级。标题说“2.0 以来的最大更新”我开始还觉得是营销话术升级完发现这次是真的把底子换掉了构建器几乎全部移到 Rust 这边插件 API 虽然还叫那个名字但其实内部已经换了一轮。这篇我就带你拆一遍为什么说它是自 Vite 2.0 以来最大的一次重构、哪些改动直接影响你日常写代码、以及从老版本升级时那些文档里不会写、但我实际踩过的坑。适合所有用 Vite 的团队和个人开发者看不管你是只写过 demo 还是维护着几十个插件都能从里面找到你需要关心的东西。1. 先把背景盘明白到底哪里“最大”了1.1 回看 Vite 2.0 当时做了什么要说“8.0 是 2.0 以来最大更新”咱得先回顾一下 2.0 当年为什么能被叫“大版本”。Vite 2.0 最大的贡献是把“开发用原生 ESM、构建用 Rollup”这套双轨架构固定下来同时正式确定了插件 API 的形态。它解决了 Vite 1 时代只能做开发服务器、没法直接用于生产构建的尴尬。可以说没有 2.0 就没有今天我能在五秒内起一个 React 项目的体验。从 2.0 到现在Vite 中间经过了 3、4、5、6、7 这几个版本。说实话中间每个版本都有不少功能但本质上还是在“开发服务器 Rollup 打包”这个框架里做优化缓存策略改了、依赖预构建出了个新的、SSR 好用了点、Plugin API 加了些钩子但内核没有发生“换引擎”级别的变化。这也是很多人到了 Vite 5、6 之后感觉速度提升不像早期那么明显的原因——该优化的大头都被前面几版吃掉剩下的只能在这里抠一点那里抠一点。1.2 8.0 的核心换引擎而不是叠功能Vite 8.0 这次的最大更新点就是它终于把“开发跑原生 ESM、构建跑 Rollup”这个伴随了 Vite 半辈子的双轨架构收敛成了一条用 Rust 写的主链路。构建、转换、依赖扫描、代码压缩这些过往分散在 JS / 原生模块里的工作现在统一交给一个底层引擎处理。这个方向其实不是冒出来的。Vite 生态里早就有 Oxc、Rolldown 这些 Rust 项目在推进只是以前它们是“试验品”你可以单独装个rolldown-vite体验一下但生产环境没人敢直接上。8.0 把它们从“实验入口”正式扶正成了默认实现这跟当年从 Vite 1 跳到 Vite 2 的选择是一样的都是“换地基”式的改动。明白了这一点你就知道 8.0 注定不是一个“加几个新配置项”的小版本而是一个需要整个插件生态跟着重新适配的大版本。2. 底层重构拆解Rust 引擎到底改了哪些关键环节2.1 Rolldown 全面接管打包路径8.0 最显眼的底层变化就是打包器默认从 Rollup 换成了 Rolldown。Rollup 是 Vite 这些年一直依赖的 JS 打包器它的模块处理机制成熟、插件生态也非常完整但瓶颈也很清楚纯 JS 实现在大型依赖图上的解析、合并、代码生成阶段耗时很厉害。Rolldown 做的事情就是把 Rollup 的 API 和插件模型在 Rust 里重新实现一遍同时尽可能保持“你的 Vite 配置、插件代码不用大改”这一承诺。在实际升级后能感受到的最直接变化就是依赖预构建时间从一个量级掉到了另一个量级。过去你项目里有几百个 npm 包冷启动第一次要等那个optimizeDeps跑完如果机器一般、依赖又多经常是十几秒到半分钟。在 8.0 里Rolldown 做这件事基本是毫秒级扫描、秒级构建而且它是并行起多个线程去处理你再看到命令行里卡在“预构建”那个位置的概率会小很多。这里要特别说一句Rolldown 不是简单把 Rollup 的源码翻译成 Rust它的模块图模型也做了调整。它把模块解析、转换、打包阶段的中间结果都以更高效的数据结构存下来所以不只是第一次快增量构建和热更新时“算哪几个模块要重跑”这件事也快了很多。简单类比就是以前修改一个文件Vite 需要在浏览器和 Node 之间来回传几轮数据、反复跳过依赖图里那些不必要的边现在它可以直接在 Rust 层面判断哪些节点真的变了然后只输出那一小片补丁。2.2 Oxc 系工具包办解析、转换与压缩除了 Rolldown8.0 还默认使用了 Oxc 系的原生工具。Oxc 是一个 Rust 写的 JavaScript 工具链集合包含解析器、AST、Transformer、压缩器等多个组件。在 8.0 之前Vite 的 esbuild 和 Rollup 组合各干各的活Esbuild 用来转换 TS 和压缩一部分代码Rollup 负责整体打包。但这会带来一个问题——ESM 和 CJS 之间、开发和生产之间会经过两条不同的解析链路行为上难免有细微差异。到了 8.0依赖解析、TS/JSX 转换、语法降级和压缩这几件事大部分都统一到 Oxc 这条链路上了。这意味着你在开发阶段看到的语法兼容性和生产构建拿到的结果会高度一致不会再出现“开发正常、build 出来报错”那种双轨分叉问题。对用了一些比较新的 ECMAScript 特性的团队来说这种一致性价值很大。不过这里也得提醒一句Oxc 在语法转换方面虽然有很高覆盖率但它毕竟不是 Babel所以如果你用了特别冷门的 Babel 插件比如某些还在提案阶段的装饰器语法方案8.0 不会自动帮你做那层转换。升级之前最好把你babel.config里挂的插件清单翻出来逐一过一遍确认哪些是“交给 Vite 就行”哪些还是要显式接入 Babel 插件链。2.3 开发服务器与构建阶段“同一套引擎”带来的连锁反应把开发服务器也从“原生 ESM”改成走同一套 Rust 引擎是这次改动中风险最大但收益也很明显的一步。以前的架构里开发模式为了让浏览器直接跑 ESMVite 只做了很轻量的转换遇到非 ESM 依赖就预构建一下基本不主动做太多重写。而生产环境要兼容老浏览器得做完整压缩、转换为旧语法。两个路径的选项、甚至语法支持层面都不同插件作者需要同时在两套逻辑里做兼容非常痛苦。8.0 在开发与构建之间大幅拉齐了处理流程。开发时虽然还是给浏览器交付 ESM但转换、依赖分析和代码生成已经走到了同一条 Rust 管线里。这让一些原本只在构建阶段生效的插件钩子在开发模式里也能用了生态适配的成本也随之降低。对我们使用者来说最直观的感受是开发模式启动更快、内存占用更稳而且一些以前只在 build 时才暴露的奇怪错误开发阶段就能提前看到。3. 除了变快还有哪些手感级的变化3.1 模块级持久化缓存不再是玄学老版本里 Vite 的缓存主要靠node_modules/.vite这个目录它做的是依赖预构建结果缓存。但应用源码本身的转换结果在开发模式下很多是存在内存里重启开发服务器就要重新转换一遍。代码量小的项目感受不明显要是碰上单体仓库几十个包打完补丁之后每天首要任务就是等冷启动。8.0 把源码级的转换结果也改成磁盘持久化了。它会给每个模块算一个内容哈希加上依赖关系、配置项等上下文信息如果什么都没变重启之后直接拿缓存结果跳过大段重复转换。我实测下来重启开发服务器的冷启动速度在某些项目上能快 40% 到 60%而且这还是一个几乎不改配置就能白拿的收益。这里分享一个使用细节Vite 8.0 的持久缓存目录跟以前的缓存目录不冲突升级后老目录会被自动清理或重建所以不用手动删node_modules底下的旧缓存。但如果你给 CI 配了缓存目录路径最好检查一下 CI 配置别把旧的.vite缓存文件继续传上去否则可能反而拖慢安装速度。3.2 启动速度与内存占用从“能忍”到“不怎么感知”我把一个约 200 个页面、300 多个直接依赖的老后台项目从 Vite 6 升到 8.0做了个粗糙的对比。冷启动大约从 6.8 秒降到 2.1 秒热更新在改动多个文件时从原来的几百毫秒到一两秒抖动降到基本稳定在百毫秒上下。内存占用方面Node 进程的 RSS 下降了 20% 左右因为很多临时 AST 和模块图结构都放在 Rust 那边管理JS 堆的压力小了。不过我也要泼一盆冷水如果你用的是 Vite 5、6 基础配置项目不大模块数量也就几百个那启动速度的体感提升可能没那么夸张。引擎再快也得先把项目结构读一遍局域网 高延迟磁盘的场景对最终体感影响其实更大。真要说最大受益者是 monorepo 里那种一个模块树包含成千上万个文件的项目。3.3 编辑器联动与错误提示对日常开发真正的“隐形翻新”8.0 在开发模式下对源码做了更细粒度的 Source Map 输出配合编辑器插件能给出更准确的“定位到报错文件”体验。以前有些 Vite 项目里报错会跳到vite:transform的虚拟文件或者一个编译后的 chunk 文件里你点进去看到一坨压缩后的代码根本没法定位。8.0 的 Source Map 默认解析到原始列级别VS Code 里 Ctrl点击可以更准确地跳到正确的行和列。错误提示本身也换了新样式。构建失败时它会像 ESLint 一样把报错的代码片段直接打印在终端里并高亮出具体是哪一行哪一列出错。这个变化对排查插件的错很有帮助因为你不必打开浏览器 DevTools 就能先在终端里看到“这个模块在哪个文件、哪个插件步骤上炸了”。老版本那种“报错在模块内部、只在浏览器 Console 里有一行堆栈”的难受体验这次算是解决了。4. 插件系统 API 变了所有插件作者都得看4.1 插件 API 兼容性看起来一样实际上有坑Vite 8.0 发布时主推的一个卖点是“插件 API 尽量保持兼容”。说实话这个“尽量”很微妙。绝大多数旧插件不用改就能在 8.0 里引用成功但如果你用到了某些内部钩子的返回格式或执行顺序就会在实际运行时报一些很莫名的错比如“Returned value is not a valid chunk”之类的。我升级后第一个碰到的问题就出在一个用enforce: pre的插件上它做的是把第三方依赖里的某些字符串替换掉。在 Vite 5 时代这个插件在transform钩子里拿到的模块代码已经经过了前一轮 esbuild 转换所以代码里很多内容是“展开”的。到了 8.0因为转换链路换成了 Oxc同样一个钩子里拿到的源码形态不同正则匹配自然就失效了。这个问题的排查思路是不要假定钩子拿到的代码是“已经被某工具格式化过”的最好用 AST 或更语义化的方式匹配目标内容。4.2 钩子运行机制的三个关键变化插件作者最应该知道的三个变化我总结一下resolveId钩子从同步为主变成大量支持并行异步。也就是说 Vite 会同时发起多个模块解析请求插件里如果用了共享状态或者全局变量很容易出现并发覆盖。写插件最好把这些状态改成局部变量或者用Map并以importee作为 key。load钩子的返回值现在会被当作不可变数据。以前有些插件会把返回的字符串再做String.replace改一改但在 8.0 里如果返回之后觉得不对再继续 mutate 可能不生效因为你拿到的可能是底层共享的 buffer直接改可能崩掉。正确的做法是返回一个新的字符串。transform钩子的可选meta参数更规范但要求使用新的签名顺序。说白了就是你的函数签名要写transform(code, id, options)不要再去拿第二个参数当作选项对象来用。这是很细节的破坏性变更但网上已经有不少插件作者中招了。4.3 对新写插件的人API 简化在哪里如果你是新手想用 Vite 写第一个插件8.0 其实是更友好的。因为官方推荐的新写法把“虚拟模块”创建和“自定义解析路径”合并成一个更直观的assets配置概念。以前想给项目加一个虚拟模块virtual:foo你得同时写resolveId和load两个钩子。现在可以直接在插件里定义一个module对象声明它叫virtual:foo然后实现generate方法就行了。另外在新增的renderChunk阶段Rolldown 提供了更丰富的 chunk 元信息包括每个 chunk 的“物理大小”“gzip 估算大小”“引用关系图”。做性能分析类插件的人以前要自己拼这些数据现在直接读钩子参数就行。我自己写了一个统计构建产物体积的插件利用这个新接口代码量几乎少了一半。5. 从旧版本到 8.0 的迁移实操记录5.1 我遇到的第一个坑依赖预构建配置项失效升级完先把package.json里的vite版本改成^8.0.0然后跑一下npm install再启动开发服务器。我用的是 Vite 7 项目配置里有这样一段optimizeDeps: { include: [axios, react, react-dom], exclude: [monaco-editor], }结果 8.0 上来就把它忽略了。后面读文档才知道新版把optimizeDeps.include和exclude的语义做了修改因为 Rolldown 的处理速度太快默认行为已经改成“启动时全量扫描依赖”不再像以前那样需要你用include去手动往预构建列表里塞一些扫描不到的东西。如果你确实需要强制排除某些依赖现在要用新的顶层配置键defineConfig.deps.exclude而不是写在optimizeDeps下面。这个升级会带来的实际影响是所有依赖扫描到的包都会在启动时被处理如果你的项目里有几个体积巨大的依赖并且它们相互之间没有依赖关系那么启动速度会比以前更依赖磁盘 IO。但总体体验仍比旧版快只是你不再需要用“人工 include”去骗 Vite 扫描了。5.2 不同版本迁移的差异速查表我整理了一份从几个常见版本升到 8.0 时需要关注的核对表方便你对照着自己排查。原版本最可能的破坏点建议处理方式Vite 5插件钩子签名与 transform 代码形式变化逐一更新有transform或enforce的插件先跑一次 build 看报错Vite 6optimizeDeps配置被废弃或半废弃改用顶层deps配置移除过时字段Vite 7依赖预构建目录变了SSR target 默认值不同清理node_modules/.vite旧缓存显式指定build.target老自定义插件resolveId返回格式可能不再兼容用新的module对象 API 重写或确认返回值是合法的解析结果这里明确说一下Vite 7 本身已经踩在 Rolldown 迁移的路上了8.0 更多是收尾和稳定所以从 7 升级最轻松如果你是从 5 或 6 直接跳那改动量会稍微大一点尤其是插件生态适配方面。5.3 构建目标与产物格式的变化8.0 对build.target的默认值也做了调整过去默认modules对应的目标大约等价于“支持原生 ESM 的现代浏览器”现在它进一步提高了基线像 Chrome 111 以上、Safari 16 以上这类版本才会被当作现代浏览器放弃了对更老浏览器的自动语法转换。如果你还在维护需要兼容老旧浏览器的项目升级后必须在build.target里显式指定一个更低的版本例如export default { build: { target: chrome87, }, }同时模块格式的默认值也更倾向于输出标准的 ESM chunk而不是以前那种经过较多兼容处理的形态。如果你依赖的是某些服务端处理过的dist产物比如直接把整个文件夹丢到特定 CDN 上运行可能需要重新检查资源引用路径。反正我的建议是把dist产物跑一遍真实的部署流程再做兼容测试不要只在本地看页面挂了没挂。6. 性能实测与一线观察变快有多快6.1 冷启动与热更新的大致量级我不想报一个精确到第三位的“理论成绩”因为环境不同、项目复杂度不同性能数字很难横向比较。但从我自己以及身边几个项目的情况来看还是能给出一个非常宽泛但可信的范围冷启动时间普遍下降 50% 到 70%千级模块项目从 5 秒左右降到 2 秒左右。批量文件保存触发的 HMR 更新从“明显感觉慢”降到“没什么感觉”。这只是把pages目录下十几个文件同时改动旧版要重新计算依赖链新版基本变化在几百毫秒内。依赖预构建在首次启动时几乎不再成为瓶颈除非你装了一个特别大的、内容特别多的包。对一个已经做了很多轮性能优化的老版本 Vite 项目来说这个再次下降的量级其实很惊人。说明“换引擎”这种改动即使上层配置看起来没变实际性能收益也是稳定的。6.2 Monorepo 场景下的实际体验Monorepo 是这次升级里受益最大的场景。传统的 Vite 在 monorepo 里跑最大的痛点是依赖预构建要同时处理工作区内部链接的包和外部的 npm 包链接关系一多扫描就非常慢。8.0 的 Rust 引擎对工作区里的软链接解析做了更高效的处理同时在启动时就开始并行扫描多个包不会像旧版那样挨个处理。我拿一个 lernayarn workspaces 的项目做过测试项目里有 20 多个子包依赖树总节点接近 8000 个。旧版第一次冷启动是 14 秒左右其中有 8 秒多花在依赖解析和预构建上。升级到 8.0 后同样的仓库在同样环境下冷启动大概 4.2 秒后续重启开发服务器时因为有了持久化缓存可以压到 2 秒内。这个提升对 monorepo 里每天要重启若干次服务器的人来说属于非常实用的节省。6.3 构建产物体积有没有变化一个大版本的性能提升之外大家也很关心产物体积。8.0 在产物体积方面没有做激进的多级分包划分默认的代码分割策略跟 7 代差别很小。但因为它底层生成的代码去除了更多冗余的 interop 包装实际产物体积通常会有 2% 到 5% 的下降。不要指望这种体积优化能替代你手动做代码分割但它确实是白赚的。我试过一个比较纯粹的 React TS 项目构建后的 gzip 体积从 235KB 降到 228KB幅度大概 3%。改动不大但对一个已经优化很久的项目来说能有这个幅度已经说明底层生成器确实比之前更擅长输出简洁的代码。7. 常见问题与排查思路实录7.1 升级后终端报错“Cannot find module”这种报错大多不是 8.0 本身的问题而是升级后依赖树里同时存在多个 Vite 版本导致的。Node 在解析vite模块时如果你没有把vite从devDependencies统一到同一个版本子依赖里某个插件可能锁定了它自己依赖的另一个 Vite 副本。解决办法很简单在根目录或package.json里做一次overrides把所有vite版本改成8.0.x然后删掉node_modules重新安装一遍。{ overrides: { vite: ^8.0.0 } }这招能解决 90% 的Cannot find module问题。剩下 10% 则可能是某些旧插件访问了 Vite 内部的不稳定目录这种只能把插件版本升到支持 8.0 的版本。7.2 插件兼容性排查步骤如果你升级之后发现某个插件不触发或者行为异常先按这个顺序排查确认插件版本是否发布了支持 8.0 的更新版本更新到最后。看插件的源码里是否引用了vite包的内部模块比如vite/dist/node/…这样的深层路径如果有基本就废了需要等插件作者重构。用最小示例逐步注释掉插件确认是它的哪个钩子引发问题。在transform钩子内部用console.log输出拿到的 code 和 id对比到底和旧版差在哪。这个排查方法我在 Vite 6 升 7 的时候就用过到 8.0 依然有效。关键是别拿一个几十个插件的大项目直接试错先做一个最小 reproductions事半功倍。7.3 产物体积或内容变化的应对如果你升级后构建出来的 dist 内容和旧版差异很大先别急着回退。很多时候是因为默认 target 变了导致代码里的工具函数展开方式不一样。比如新版不再加载某些 polyfill因为目标浏览器已经原生支持了。这种变化是预期的你需要做的是部署后跑一轮回归测试。如果差异实在太大可以先手动在build.target里指定旧版浏览器版本再对比一次。如果指定到chrome87之后产物体积和旧版接近那基本可以判断就是 target 基线调整导致的。8. 最后分享一个小技巧我自己在后面几天继续用 8.0 时发现一个新的好处它在处理构建日志时会把最耗时的几个模块直接排序列出来并且标注“这个模块为什么不能缓存”。你只要在终端里开满DEBUGvite:*模式跑一次构建就能看到一张模块耗时分析表。这张表对定位项目里的性能瓶颈非常有用比以前靠猜或者靠 Chrome Performance 去翻网络请求直观多了。希望你升级顺利。如果遇到怪问题建议先跑一个vite --debug看看底层到底在哪一步停住通常比查论坛快很多。