我试图优化 Vite 的拆包,结果首屏慢了 10 倍

拆包前 拆包后

有些优化不需要做,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 ms
  • index-37f01f92.css --- 328 kB,327 ms
  • login-c327e251.js --- 9.7 kB,51 ms
  • 背景图 --- 69.4 kB,290 ms

几个文件并行加载,谁也不等谁,浏览器并发请求充分利用,800ms 左右首屏就搞定了。

拆包后(慢)

我手动配置之后,局面变成了:

  • vendor-a32a4159.js --- 1,968 kB6.56 秒

将近 2MB 的 vendor 文件,光下载就花了 6 秒多。

更致命的是,这个巨大的 vendor 文件阻塞了后续所有资源的加载。后面的 element-plus、css、业务代码,全都在等它下载完、解析完、执行完。

一个 2MB 的巨石文件,变成了整个首屏的唯一瓶颈

为什么我的"优化"适得其反?

冷静下来之后,我分析了三个层面的原因。

1. 浏览器不是水管,越大越慢

我当时的想法很简单:反正总大小不变,打包成一个文件少几个请求,不是更快吗?

但浏览器不是这么工作的。

2MB 的 JS 文件,浏览器需要:

  1. 下载(6.56 秒)
  2. 解析(Parse)
  3. 编译(Compile)
  4. 执行(Execute)

这个过程中,浏览器的主线程被完全占据,页面无法渲染,用户只能看着白屏。

而拆成多个小文件时,浏览器可以:

  • 并行下载多个文件
  • 边下载边解析执行
  • 关键资源优先加载

总大小相同,但加载体验天差地别。

2. 缓存策略被我亲手毁了

我原本想通过拆包提升缓存命中率,结果反而把它毁了。

拆包前,Vite 默认策略下,每个第三方依赖都有自己的 chunk:

  • vue-xxx.js
  • element-plus-xxx.js
  • vueuse-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
用户普遍在弱网环境 ⚠️ 谨慎 需要更精细的控制,但依然要数据支撑

第四步:如果真要拆包,怎么做?

如果确实需要自定义,记住几个原则:

  1. 不要把所有东西都塞进一个 vendor
  2. 按包名拆分,而不是按类型拆分
  3. 首屏关键路径上的资源不要拆得太碎
  4. 改完之后一定要用数据验证

一个相对安全的拆包方案:

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,而是一个朴素的道理:

一个工具如果经过了成千上万项目的验证,它的默认配置大概率是在大多数场景下最优的。我们作为使用者,在试图"优化"它之前,最好先搞清楚:

  1. 默认配置为什么这样设计?
  2. 我的场景真的需要自定义吗?
  3. 有没有数据支撑我的优化方向?

这次 "反向优化" 让我学会了一件事:

优化的本质是解决问题,而不是满足自己"动手调一调"的心理需求。数据先行,结果说话。

如果你现在正看着 Vite 的配置文档,琢磨着怎么"优化"拆包策略,我的建议是:

先跑一遍 Lighthouse,看看 LCP 到底多少。如果数据告诉你"没问题",那就真的别动它。

毕竟,有些优化,不做就是最好的优化。


如果这篇文章帮你省了几个小时(甚至一下午)的折腾时间,点个赞再走吧 👋


本文首发于掘金,欢迎转发讨论。你在项目中遇到过哪些"反向优化"的案例?评论区聊聊 👇

相关推荐
PBitW2 小时前
GPT训练我的第二天,我表示不过如此!!!😕😕😕
前端·javascript·面试
用户99045017780092 小时前
学习了AI修图,我把自己闲鱼出租房照片整成airbnb风格了
前端
kyriewen3 小时前
白宫直接给 OpenAI 下了限制令,GPT-5.6 不能随便放出来了
前端·javascript·面试
PedroQue994 小时前
Vite插件v0.2.6:架构优化与自动化升级
前端·vite
threerocks5 小时前
什么?我连 A2A、MCP 都没学会,现在又来了 AG-UI、A2UI.
前端·aigc·ai编程
牛奶6 小时前
如何自己写一个浏览器插件?
前端·chrome·浏览器
亿元程序员6 小时前
为什么Cocos都4.0了还有人用2.x?
前端
MomentYY7 小时前
AI 到底是“懂”,还是在“猜”?
前端·人工智能·ai编程
鹏毓网络科技7 小时前
Cursor Rules 文件配置实战:3 个隐藏参数让我每月少写 40% 样板代码
前端·github