2026 年 6 月末,Vite 8.1 正式发布。这个版本没有破坏性变更,但发布公告标题里那句话很反常:
Bundled Dev Mode------开发时打包,大型项目冷启动最高 15 倍提速。
Vite 从 2020 年立项到 8.0 一路封神,靠的就是一个卖点------"开发时不打包"(No Bundle Dev)。它用原生 ES Module 让浏览器按需请求模块,把 webpack 那种"全量打包才能启动"的模式钉在了历史的耻辱柱上。
现在 Vite 自己站出来说:开发时也可以打包,某些项目应该打包。
不是回头路,是承认------七年前对的假设,2026 年的项目规模已经撑不住了。
这篇文章拆三层:为什么"不打包"这条路开始走不通、Bundled Dev Mode 到底做了什么、什么样的项目现在就该开。文末给了一份可直接用的 Vue + Vite 8.1 迁移清单和 bench 脚本。
一、Vite 最引以为傲的设计,为什么开始撑不住了
先复盘 Vite 的原始设计。
2020 年的前端世界,webpack 是霸主。它的工作模式是:npm run dev 一按,先把项目的所有源码 加上所有依赖塞进依赖图,一起打成一个(或者几个)bundle,然后开发服务器再启动。项目越大,启动越慢------十几个 Vue 页面 30 秒,几十个页面等 1 分钟以上。
Vite 干了一件在当时看起来"反常识"的事:dev server 启动时不打包。
它的逻辑很直接:
- 浏览器已经原生支持
<script type="module">和import了 - 那为什么还要在服务端把模块拼成 bundle?
- 直接让浏览器按需请求模块,需要哪个就发一个请求
- Vite 只负责把 TS / JSX / Vue SFC 现场转成浏览器能读的 JS,然后回给浏览器
这个模式的收益是恐怖的------冷启动从 30 秒变成 300 毫秒,快了 100 倍。项目多大都无所谓,因为首屏只请求首屏用到的模块,而不是把整个项目打包出来。
于是 Vite 用五年时间干掉了 webpack。到 2026 年,Vite 的周下载量已经稳定在 1.4 亿以上,比 Vite 7 累计下载量还多。
问题从大型项目开始冒出来。
一个 Vue 企业级应用,几百个页面、几千个组件、上万个模块。你以为 Vite 会秒启动?
现实是:
- 依赖预构建(把 node_modules 里的 CJS 包转成 ESM)第一次要跑几十秒
- 首屏一打开,浏览器要向 dev server 发几百个 module 请求
- 每个请求都要走一遍插件链:resolve、load、transform、HMR 注册
- 全量刷新(Full Reload)时,浏览器要重新发几百个请求,几秒钟没画面
- 你的路由跳到新页面,又是几十上百个模块要拉
Vite 官方也承认了这一点,8.1 发布公告里写得很直白:
真正的问题已经不是构建耗时,而是模块数量爆炸导致开发服务器请求成本失控。
这个问题在小项目里根本不存在,但一旦项目突破 5000 模块(一个中等 SaaS 后台就到了),"每个模块一次请求"的物理开销开始压过"不打包"的收益。
打个比方:Vite 原来的模式像便利店,你要什么拿什么,来去自由。但当你要买 1000 件商品时,一件一件排队结账反而比一次性推购物车结账慢得多。
二、Bundled Dev Mode:不是回到 webpack,是"用 Rolldown 提前打好,用 HMR 精准替换"
Vite 8.1 的解法叫 Bundled Dev Mode(打包开发模式)。名字听起来像回到 webpack,但实现思路完全不同。
传统 Vite dev(保留):
css
启动项目 → 只做依赖预构建 → 开发服务器就绪
浏览器请求 → 逐个模块 transform → 发回浏览器(几百次请求)
Bundled Dev Mode(新增,默认关):
启动项目 → Rolldown 一次性打包整个应用 → 开发服务器就绪
浏览器请求 → 直接拿打好的 chunk → 一次或几次请求搞定
关键不同有三处:
① 打包器换成了 Rolldown(Rust 写的) Vite 8 已经把生产构建从 esbuild + Rollup 换成了 Rolldown 单打包器。8.1 让开发模式也复用这条 Rust 链路,等于是"生产构建器 + 保留 HMR 的能力"。Rust 的原生性能保证了打包耗时是可接受的------它不是 webpack 那种 JS 慢慢跑的打包器。
② HMR 依旧走精准热替换 这是最容易被误解的地方。开了 Bundled Dev 之后,你依然能得到秒级 HMR。改一个 Vue 组件,只有那个组件被替换,页面状态保留。Vite 团队为此在 Rolldown 里做了 chunk 级的 HMR 边界追踪------打包只发生在启动那一下,之后的热更新走的还是精准替换。
用他们自己的话:
Bundled Dev Mode 的核心是"预打包 + 精准 HMR",不是"每次改动全量重打"。
③ 首屏请求数从几百下降到个位数 这是 Linear 团队实测最夸张的一条:Linear 首屏请求数减少 90%。原来几百次 module fetch,现在几个 chunk 打包好一次拉完。移动端和弱网环境下体感差异极大。
开启方式
方式一,写进 vite.config.ts:
ts
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
experimental: {
bundledDev: true,
},
})
方式二,命令行临时开:
bash
vite --experimental-bundle
两种都行,前者持久化写在配置里,后者适合 A/B 对比。注意 experimental 前缀------这仍然是实验特性,8.1 里不是默认打开。
三、一手实测数据:三组硬指标
Vite 8.1 发布时官方给了两组基准数据,加上 Linear 团队的生产项目实测博客,我把它们放在一张表里:
| 场景 | 传统 Vite Dev | Bundled Dev Mode | 提升倍数 |
|---|---|---|---|
| 10000 React 组件冷启动 | 基准 | 1/15 | 15x |
| 10000 React 组件整页重载(Full Reload) | 基准 | 1/10 | 10x |
| Linear 生产项目冷启动 | 基准 | 1/3 | 3x |
| Linear 页面全量重载 | 基准 | -40% | 1.67x |
| Linear 首屏模块请求数 | 基准 | 1/10 | 10x(−90%) |
| HMR 单组件热更新 | ≈50ms | ≈50ms | 不变 |
据《Vite 8.1 正式发布!启动提速 15 倍,加载提速 10 倍》(juejin.cn/post/765605...),这些数字来自官方基准仓库和 Linear 团队的生产环境实测博客。
有三个细节值得单独拎出来:
① "10000 组件"是官方压测基准,不代表你的项目
15 倍这个数字是 Vite 团队构造的极端场景------10000 个 React 组件的合成项目,模块数量刻意堆到极限。你自己的项目大概率没这么夸张,收益不会是 15 倍。
真实项目的参考值是 Linear:中等规模的 SaaS 后台,冷启动 3 倍,全量重载 1.67 倍,请求数减少 90%。这才是可参考的量级。
② HMR 单组件热更新几乎不变
这一条特别重要。很多人担心"打包了 HMR 是不是就变慢了"------不会。Vite 8.1 保留了 Vite 原有的精准 HMR 机制,改一个组件就替换一个 chunk 的模块。日常写代码时的体感和以前一样。
Bundled Dev 换掉的只有冷启动 + 全量刷新这两条"慢路径"。热开发流程不受影响。
③ 请求数减少的连锁收益
首屏请求数 −90% 不只是"快 10 倍"这么简单。它还带来:
- 弱网 / 移动 dev 环境体验大幅改善
- Chrome DevTools 的 Network 面板可读性提升(原来几百条请求,现在几条)
- 反向代理(比如你在 Electron 里用自定义 protocol 加载 dev server)的调试成本下降
- CI/CD 场景下 e2e 测试的启动更稳定
四、内部原理:为什么 Rolldown 能"打包一次不影响 HMR"
这一节偏原理,赶时间可以跳到第五节看决策。
Vite 8.1 的实现分四步:
第一步:启动时全量 Rolldown 打包
npm run dev 时,Rolldown 会走一遍完整的模块解析 + 打包流程,但产出的不是 dist/ 里的最终 chunk,而是"dev 优化过"的 chunk------保留了模块 ID、source map、HMR 客户端注入点。这个过程用 Rust 完成,即便是 10000 模块的项目,也就几秒钟。
第二步:dev server 直接分发这些 chunk
浏览器打开页面,dev server 分发的不再是"每个模块一次请求",而是几个大 chunk。首屏可能只有 3-5 个 chunk(app、vendor、runtime 之类)加上 CSS。请求数从几百降到个位数,这是 −90% 数据的物理来源。
第三步:文件变化时精准热替换
你改了一个 Vue 组件(比如 Foo.vue):
- Vite dev server 观察到文件变化
- Rolldown 只重新处理
Foo.vue单个模块(不是整个 chunk) - 生成一个 patch,通过 HMR 通道推送到浏览器
- 浏览器执行 patch,替换那个模块的导出,触发 Vue 的组件级重挂载
这一步是关键------它不会重新打包整个 chunk,只推送模块级 diff。所以 HMR 延迟依然是几十毫秒。
第四步:热更新累积到一定量再重打
如果你连续改了几十个文件(比如 rebase 之后拉了一大堆变更),HMR 会累积 patch。当累积超过阈值时,Vite 会在后台重新触发一次 Rolldown 增量打包,把状态"归一"。这个过程对开发者是透明的,你不会看到"全量刷新"。
为什么这条路 esbuild 走不通
有人问:既然要打包,为什么不继续用 esbuild?8.0 之前的 Vite 用 esbuild 做依赖预构建也很快啊。
答案在 Vite 团队的解释里:esbuild 的模块图不支持精准 HMR。它是"打好扔出去"的一次性工具,不维护模块间的热更新边界。你要把它改造成"能 HMR 的 dev bundler"成本极高。
Rolldown 从设计之初就把 dev 和 prod 视为同一条 pipeline 的两种输出。开发模式在 Rust 层维护模块图和 HMR 边界,改一个文件时能精准定位到"哪个 chunk 的哪个模块要被 patch"。这是 esbuild 架构上做不到的。
据《Vite 8 + Rolldown Migration Guide》(nexgismo.com),Rolldown 是"purpose-built for Vite"(为 Vite 定制)。8.1 的 Bundled Dev Mode 是这个定制关系兑现的第一个大特性------dev 和 prod 用同一条 Rust 打包链。
五、什么时候该开、什么时候先别开
Vite 官方明确说 Bundled Dev 是"新增选项,不是替换"。小项目继续 no-bundle,大项目按需切换。给一份决策表:
| 项目类型 | 建议 | 原因 |
|---|---|---|
| 组件库 / demo / 教程项目(<100 模块) | 别开 | no-bundle 秒启动已经足够,打包反而多花几百毫秒 |
| 中小型 Web 应用(<1000 模块) | 可以试,但收益有限 | 冷启动可能从 2s 到 1s,体感提升不大 |
| Vue 3 中后台 SaaS(1000-5000 模块) | 建议开 | 冷启动 5-10s 是常态,能砍到 1-3s |
| 大型 Monorepo / 企业级应用(>5000 模块) | 必须开 | no-bundle 已经痛到不可用,收益最大 |
| Electron 主进程 + 渲染进程双端项目 | 建议开 | dev 时 renderer 端首屏请求数是主要 bottleneck |
| AI 平台 / 低代码平台(模块动态生成) | 谨慎试 | 动态 import 多、模块生成路径复杂,可能踩坑 |
| 生产环境 / preview 分支 | 别开 | experimental 阶段,生产别用 |
三条"暂时不能开"的硬约束
① 用了不兼容的第三方插件
官方明说:Bundled Dev Mode 只保证兼容 Vite 官方基础插件(@vitejs/plugin-vue、@vitejs/plugin-react、@vitejs/plugin-legacy 等)。第三方插件如果依赖了旧 Vite 的模块图 API(load / transform 的执行顺序假设),可能会挂。
先在业务分支上开一天看看,别直接推 main。
② 用了 experimental.renderBuiltUrl
8.1 新增的 Chunk Import Map 和 renderBuiltUrl 互斥。如果你项目里用了 renderBuiltUrl 做资源 URL 重写(很多 CDN 部署场景会用),Bundled Dev 里的 Chunk Import Map 相关能力会与之冲突。
③ 项目里有大量 dynamic import 且路径是动态字符串
Rolldown 静态分析 dynamic import 时对"完全动态的路径字符串"支持不如运行时按需加载。比如:
ts
// 静态可分析 ------ 没问题
const mod = await import('./views/Home.vue')
// 半动态 ------ Rolldown 用 glob 也能处理
const mod = await import(`./views/${page}.vue`)
// 完全动态 ------ Bundled Dev 可能定位不到
const mod = await import(rawPath)
第三种情况在插件系统、微前端、低代码场景常见,升级前先 grep 一下 import( 的用法。
六、Vue 项目实操:三步试水 + bench 脚本
对 Vue 生态来说,Vite 8.1 的适配没有任何摩擦。@vitejs/plugin-vue 官方已经跟 Rolldown 完全对齐。
第一步:确认 Node 版本
Vite 8 要求 Node.js 20.19+ 或 22.12+。本地、CI、Dockerfile 都要对齐。Electron 项目额外要注意 renderer 端的 Node 版本(Electron 内嵌 Node):
bash
node -v # 至少 v20.19 或 v22.12
第二步:安装 Vite 8.1
bash
pnpm add vite@latest
如果你之前是 Vite 7,中间会自动经过 Rolldown 迁移。8.0 已经跑通的项目升 8.1 是无缝的。
第三步:配置 Bundled Dev(先建业务分支再改)
ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
experimental: {
bundledDev: true, // 主开关
},
// Chunk Import Map 优化缓存(可选,与 renderBuiltUrl 互斥)
// experimental: {
// bundledDev: true,
// chunkImportMap: true,
// },
})
bench 脚本:跑 5 次取平均值再决策
别凭感觉。第一次跑可能有 node_modules 缓存 warm-up,第二次可能后台在跑别的任务。放一个脚本进项目:
js
// scripts/bench-dev-startup.mjs
import { spawn } from 'node:child_process'
import { performance } from 'node:perf_hooks'
const RUNS = Number(process.env.RUNS ?? 5)
const READY_PATTERN = /ready in \d+/i
const results = []
async function runOnce(index) {
return new Promise((resolve) => {
const start = performance.now()
const proc = spawn('pnpm', ['dev'], { stdio: ['ignore', 'pipe', 'pipe'] })
let done = false
proc.stdout.on('data', (chunk) => {
const line = chunk.toString()
process.stdout.write(line)
if (!done && READY_PATTERN.test(line)) {
done = true
const cost = (performance.now() - start) / 1000
results.push(cost)
console.log(`\n[run ${index}] ready in ${cost.toFixed(2)}s`)
proc.kill('SIGTERM')
resolve()
}
})
proc.on('exit', () => {
if (!done) {
console.log(`[run ${index}] failed to detect ready`)
resolve()
}
})
})
}
for (let i = 1; i <= RUNS; i += 1) {
await runOnce(i)
await new Promise((r) => setTimeout(r, 2000)) // 冷却 2s
}
const avg = results.reduce((s, v) => s + v, 0) / results.length
const min = Math.min(...results)
const max = Math.max(...results)
console.log(`\navg=${avg.toFixed(2)}s min=${min.toFixed(2)}s max=${max.toFixed(2)}s`)
跑两组对比:
bash
# 组一:传统模式(把 vite.config 里 bundledDev 改成 false)
RUNS=5 node scripts/bench-dev-startup.mjs
# 组二:Bundled Dev
RUNS=5 node scripts/bench-dev-startup.mjs
对比 avg 就知道你的项目值不值得开。经验值:
- avg 提升 <20%:收益不明显,别切
- avg 提升 20%-50%:看团队痛点程度,如果每天都吐槽启动慢就切
- avg 提升 >50%:直接切,日均省几分钟就是几百块钱工资
Electron 场景的一个坑
Electron 项目里,renderer 通常跑在 dev server 上。开了 Bundled Dev 后,第一次启动 renderer 会比原来慢一点(因为要打包),但一旦 dev server 起来了,Electron 主进程 reload renderer 会快得多------请求数少了,dev server 分发速度快得多。
这个 trade-off 在 Electron 场景通常是划算的,因为你在 Electron 里一天 reload 几十次 renderer,冷启动只有一次。
七、Vite 8.1 顺带的四个改进(都能马上用)
Bundled Dev 是本次头条,但 8.1 还塞了四个小改进,几乎每个都有实用价值。
① Chunk Import Map:解决"改一行工具函数,全站缓存失效"
老问题:你改了一个 utils 函数,Rolldown 会重算这个模块的 chunk hash。因为其他 chunk 依赖了这个 hash,它们的 hash 也跟着变------用户浏览器缓存的旧文件全部失效,重新拉一遍。
Chunk Import Map 用浏览器原生的 Import Maps 特性把这个链路解耦:
- 每个 chunk 用逻辑 ID (比如
utils.js)而不是 hash 路径引用其他 chunk - 具体 hash 到路径的映射写在 HTML 里的
<script type="importmap"> - 改 utils 只影响 utils 那一个 chunk 的 hash 和 import map,其他 chunk 完全不动
结果:大项目首屏加载时的浏览器缓存命中率显著提升。
ts
// vite.config.ts
export default defineConfig({
experimental: {
chunkImportMap: true, // 开启
},
})
⚠️ 注意 :这个特性和 experimental.renderBuiltUrl 互斥,只能二选一。
② Wasm ESM:不装插件直接 import .wasm
以前你要在项目里用 WebAssembly:装 vite-plugin-wasm,配 loader,写一堆 magic import 语法。
8.1 直接内置了 Wasm ESM 标准:
ts
import { add } from './add.wasm'
console.log(add(1, 2)) // 3
对于用了 Rolldown / Oxc / SWC / Biome 这些 Rust 工具链的项目,未来它们的 WASM 版本可以直接在浏览器跑。
你的 Electron 项目如果有性能敏感的计算逻辑(比如图像处理、音频分析),可以考虑用 Rust 写完编译成 wasm,Vite 里直接 import,比 native binding 简单得多。
③ Lightning CSS 补齐短板:可以用外部 CSS 和插件依赖了
Vite 8 已经默认用 Lightning CSS 做 CSS 压缩,但当时有些边缘场景(外部 CSS 引入、插件自定义依赖)不支持。8.1 补齐了。
现在可以整体把 CSS 转换从 PostCSS 切到 Lightning CSS,编译和压缩都更快:
ts
export default defineConfig({
css: {
transformer: 'lightningcss',
},
})
官方明说:未来大版本会把 Lightning CSS 设成默认。现在切过去,等于提前对齐未来。
④ import.meta.glob 支持 caseSensitive 选项
老 bug:Windows 文件系统不区分大小写,Mac 区分。你在 Windows 上写 import.meta.glob('./views/**/Page.vue'),两个都匹配;一 push 到 CI(Linux),只有 Page.vue 匹配,page.vue 就漏了。
8.1 加了显式选项:
ts
const modules = import.meta.glob('./dir/module*.js', {
caseSensitive: false, // 显式跨平台大小写不敏感
})
场景边缘,但踩过的人都知道疼。加上这一行防线,跨平台协作稳一半。
八、一次教科书式的自我否定
写到这里可以退一步看这次升级的叙事。
Vite 从 2020 年起,靠"No Bundle Dev"这一个核心创新终结了 webpack。这套设计的假设是:
浏览器原生 ESM + 按需加载 > 预打包 + 服务端分发
七年过去,这个假设开始出现裂缝------在小到中等项目上依然成立,在大型项目上被模块数量的物理开销压垮。
Vite 8.1 做的事,本质是承认这一点。它不是抛弃 No Bundle Dev(小项目继续用),而是新增一条"打包路径"作为大项目的兜底。你会发现这个设计和当年 Vite 挑战 webpack 时的对比表几乎反过来:
| 2020 年 Vite vs webpack | 2026 年 Vite 8.1 vs Vite 7 | |
|---|---|---|
| 冷启动策略 | 不打包,按需加载胜出 | 打包 + 精准 HMR 胜出 |
| 请求数 | 少 vs 多 | 少 vs 多 |
| 首屏体验 | 秒开胜出 | 秒开胜出 |
| 大项目痛点 | webpack 30s+ | Vite 无打包也 10s+ |
指标没变,赢家换了。因为项目规模变了,物理约束的分界线跟着移。
我觉得这才是这次升级真正值得写的地方------技术选型没有永远正确的答案。当年做 No Bundle Dev 是对的,2026 年做 Bundled Dev 也是对的。工具的价值不在于"我这套设计多么优雅",而在于"我能持续解决用户当下的问题"。
Vite 团队敢在自己最引以为傲的设计上开一个"回头路"的实验特性,还是需要点勇气的。这比很多"我们的架构十年不变"的产品负责得多。
九、升级 checklist(可复用)
发一份可以直接抄的 checklist 给团队:
ini
[ ] Node.js 版本对齐 20.19+ 或 22.12+(本地/CI/Docker/Electron)
[ ] pnpm add vite@latest(升到 8.1)
[ ] 检查所有第三方 Vite 插件的 Rolldown 兼容声明
[ ] 在业务分支开启 experimental.bundledDev
[ ] 运行 bench-dev-startup.mjs 跑 5 次冷启动对比
[ ] 手动 reload 5-10 次,测 HMR 是否正常
[ ] 检查是否用了 experimental.renderBuiltUrl(与 Chunk Import Map 互斥)
[ ] grep 全项目 import( 用法,排查完全动态路径
[ ] 生产环境保持 build 走 Vite 8 原生(不受 Bundled Dev 影响)
[ ] 团队内部对齐使用规范,避免有人开有人不开导致 bench 数据混乱
以上任何一步失败就打住,别硬升。8.1 是好特性,但 experimental 不适合直接推生产依赖的开发工作流。
参考资料
- Vite 8.1 官方发布博客:vite.dev/blog/announ...
- 《Vite 8.1 正式发布!启动提速 15 倍,加载提速 10 倍》juejin.cn/post/765605...
- 《尤雨溪再出手!Vite 8.1 正在重塑前端开发体验》51cto.com/article/847...
- 《Vite 8 + Rolldown Migration Guide》nexgismo.com/blog/vite-8...
- Rolldown 官方文档:rolldown.rs
- Linear 团队 Bundled Dev Mode 实测博客(Vite 官方发布公告引用)
本内容由AI辅助生成