拆包前
拆包后 
有些优化不需要做,Vite 的默认配置就是最佳实践
写在前面
先给大家看一组真实的数据对比。
这是我同一个 Vue3 + Vite 项目,在修改了拆包配置前后的首屏加载情况:
拆包前(Vite 默认配置):
| Name | Status | Type | Initiator | Size | Time |
|---|---|---|---|---|---|
| index-712a6fc0.js | 200 | script | login:16 | 340 kB | 319 ms |
| index-37f01f92.css | 200 | stylesheet | login:17 | 328 kB | 327 ms |
| login-c327e251.js | 200 | script | index-712a6fc0.js:5 | 9.7 kB | 51 ms |
| login-9a94892b.css | 200 | stylesheet | index-712a6fc0.js:5 | 1.2 kB | 49 ms |
| login | 200 | document | /login | 1.0 kB | 39 ms |
| 1710832132bj.webp | 200 | webp | login-9a94892b.css | 69.4 kB | 290 ms |
首屏总耗时:约 800ms
拆包后(手动配置 manualChunks):
| Name | Status | Type | Initiator | Size | Time |
|---|---|---|---|---|---|
| vendor-a32a4159.js | 200 | script | login:17 | 1,968 kB | 6.56 s |
| element-plus-ff15f869.css | 200 | stylesheet | login:21 | 327 kB | 507 ms |
| element-plus-00e56c40.js | 200 | script | login:19 | 276 kB | 584 ms |
| vue-ecosystem-ec9ba455.js | 200 | script | login:18 | 102 kB | 215 ms |
| vendor-501cf061.css | 200 | stylesheet | login:20 | 15.1 kB | 155 ms |
| index-4d3f05e7.js | 200 | script | login:16 | 13.3 kB | 84 ms |
首屏总耗时:约 7.5 秒
同样的项目、同样的网络环境,只是改了一下 vite.config.ts 里的 manualChunks 配置,首屏耗时从不到 1 秒变成了 7.5 秒,慢了将近 10 倍。
那天下午的我:

本文不是要教大家怎么配置 Vite,而是想分享一个认知:Vite 的默认配置比你想象的要聪明得多,优化之前,先确认问题真的存在。
我到底做了什么?
先还原一下我当时的天真操作。
项目用是 Vue3 + Vite,首屏加载大概 800ms,其实已经挺快了。但我总觉得:
- "把第三方库单独打成 vendor,可以利用浏览器缓存啊"
- "node_modules 那么大,不应该跟业务代码混在一起"
- "手动控制拆包,肯定比自动的更强"
于是我在 vite.config.ts 里加了这样一段配置:
ts
php
export default defineConfig({
build: {
cssCodeSplit: true,
rollupOptions: {
output: {
manualChunks: (id) => {
if (id.includes('node_modules')) {
if (id.includes('element-plus')) {
return 'element-plus'
}
if (id.includes('vue') || id.includes('pinia') || id.includes('vue-router')) {
return 'vue-ecosystem'
}
return 'vendor'
}
}
}
}
},
})
看着这段配置,我当时甚至有点得意:
- ✅ vue 全家桶单独打包 → 缓存友好
- ✅ element-plus 单独打包 → 按需加载
- ✅ 工具库单独打包 → 结构清晰
完美!😎
然后 npm run build,部署,刷新页面,打开 Network 面板...
笑容逐渐凝固。
数据告诉我:什么叫"反向优化"
对比两张图,问题其实一目了然。
拆包前(快)
Vite 默认的拆包策略下,资源是这样的:
index-712a6fc0.js--- 340 kB,319 msindex-37f01f92.css--- 328 kB,327 mslogin-c327e251.js--- 9.7 kB,51 ms- 背景图 --- 69.4 kB,290 ms
几个文件并行加载,谁也不等谁,浏览器并发请求充分利用,800ms 左右首屏就搞定了。
拆包后(慢)
我手动配置之后,局面变成了:
vendor-a32a4159.js--- 1,968 kB ,6.56 秒
将近 2MB 的 vendor 文件,光下载就花了 6 秒多。
更致命的是,这个巨大的 vendor 文件阻塞了后续所有资源的加载。后面的 element-plus、css、业务代码,全都在等它下载完、解析完、执行完。
一个 2MB 的巨石文件,变成了整个首屏的唯一瓶颈。
为什么我的"优化"适得其反?
冷静下来之后,我分析了三个层面的原因。
1. 浏览器不是水管,越大越慢
我当时的想法很简单:反正总大小不变,打包成一个文件少几个请求,不是更快吗?
但浏览器不是这么工作的。
2MB 的 JS 文件,浏览器需要:
- 下载(6.56 秒)
- 解析(Parse)
- 编译(Compile)
- 执行(Execute)
这个过程中,浏览器的主线程被完全占据,页面无法渲染,用户只能看着白屏。
而拆成多个小文件时,浏览器可以:
- 并行下载多个文件
- 边下载边解析执行
- 关键资源优先加载
总大小相同,但加载体验天差地别。
2. 缓存策略被我亲手毁了
我原本想通过拆包提升缓存命中率,结果反而把它毁了。
拆包前,Vite 默认策略下,每个第三方依赖都有自己的 chunk:
vue-xxx.jselement-plus-xxx.jsvueuse-xxx.js
哪天 element-plus 升级了,只有 element-plus 的 chunk 缓存失效,其他都不变。
而我手动配置后:
text
vendor-a32a4159.js ← 包含了 vue + vue-router + pinia + axios
哪天 axios 发了个小版本,整个 1,968 kB 的 vendor 全部缓存失效,用户要重新下载将近 2MB 的文件。
本来想优化缓存,结果缓存命中率反而大幅下降。
3. Vite 的默认策略,比我聪明得多
Vite 内置了 splitVendorChunkPlugin,它的策略简单但有效:
ts
less
// Vite 内部大致逻辑
if (id.includes('node_modules')) {
// 每个第三方包单独成一个 chunk
// 利用浏览器缓存
}
每个第三方依赖独立成 chunk,并行加载,互不影响。
这个策略已经经过了成千上万项目的验证,在绝大多数场景下都是最优解。
正确的优化思路应该是什么?
这次经历让我重新思考了"前端优化"这件事。
第一步:不要凭直觉做优化
我犯的最大错误是:在没有性能问题的情况下,"凭感觉"做优化。
优化的正确流程应该是:
text
用户反馈/监控告警 → Lighthouse/Performance 实测 → 定位瓶颈 → 针对性优化 → 再次实测对比
而不是:
text
我觉得这里可以优化 → 改配置 → 变慢了 → 懵了
第二步:用数据说话
优化前,先问自己几个问题:
- 🟢 首屏加载时间是多少?(LCP)
- 🟢 用户主要在哪个环节感到卡顿?
- 🟢 是网络慢?还是渲染慢?还是执行慢?
- 🟢 有没有监控数据支持你的优化方向?
没有数据支撑的优化,大概率是在意淫。
第三步:什么时候才需要自定义拆包?
Vite 的默认配置已经足够好,但确实有一些场景需要手动干预:
| 场景 | 是否需要自定义 | 说明 |
|---|---|---|
| 项目刚启动,首屏 800ms | ❌ 不需要 | 别动,已经很好了 |
| 首屏加载 3s+,Lighthouse 评分低 | ⚠️ 先定位 | 找到瓶颈再动手 |
| 某个超大库(如 ECharts)首屏不需要 | ✅ 需要 | 单独拆出来懒加载 |
| 多个入口共享大量公共代码 | ✅ 需要 | 抽离 common chunk |
| 用户普遍在弱网环境 | ⚠️ 谨慎 | 需要更精细的控制,但依然要数据支撑 |
第四步:如果真要拆包,怎么做?
如果确实需要自定义,记住几个原则:
- 不要把所有东西都塞进一个 vendor
- 按包名拆分,而不是按类型拆分
- 首屏关键路径上的资源不要拆得太碎
- 改完之后一定要用数据验证
一个相对安全的拆包方案:
ts
javascript
manualChunks(id) {
if (id.includes('node_modules')) {
// 每个包单独拆分,而不是全合并
const pkgName = id.split('node_modules/')[1].split('/')[0]
return `vendor-${pkgName}`
}
}
或者,直接使用 Vite 官方提供的插件,一行搞定:
ts
javascript
import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
plugins: [splitVendorChunkPlugin()]
})
最后想说的话
Vite 团队的默认配置,比你凭直觉手动配置要聪明得多。
这句话不是吹捧 Vite,而是一个朴素的道理:
一个工具如果经过了成千上万项目的验证,它的默认配置大概率是在大多数场景下最优的。我们作为使用者,在试图"优化"它之前,最好先搞清楚:
- 默认配置为什么这样设计?
- 我的场景真的需要自定义吗?
- 有没有数据支撑我的优化方向?
这次 "反向优化" 让我学会了一件事:
优化的本质是解决问题,而不是满足自己"动手调一调"的心理需求。数据先行,结果说话。
如果你现在正看着 Vite 的配置文档,琢磨着怎么"优化"拆包策略,我的建议是:
先跑一遍 Lighthouse,看看 LCP 到底多少。如果数据告诉你"没问题",那就真的别动它。
毕竟,有些优化,不做就是最好的优化。
如果这篇文章帮你省了几个小时(甚至一下午)的折腾时间,点个赞再走吧 👋
本文首发于掘金,欢迎转发讨论。你在项目中遇到过哪些"反向优化"的案例?评论区聊聊 👇