项目用 Vite + React,整体打包体积其实还能接受,但有一次做性能排查时发现了一个非常刺眼的问题:
build 后最大的单个 JS 文件超过了 4MB
这个 chunk 几乎参与了所有页面的加载,哪怕走了 gzip,对首屏加载依然非常不友好。
所以这次优化的目标也很明确:
不追求整体包体积数字好看,只针对这个“最大 JS Chunk”做瘦身。
最终结果是:
最大 JS Chunk 从 4000+ KB 压缩到了 500+ KB(未压缩体积)
下面记录一下这次优化的全过程。
一、先定位问题:为什么会有 4MB 的 JS 文件?
第一步没有急着改配置,而是先做了打包分析。
我在项目里加了 rollup-plugin-visualizer,重点看两个点:
- 哪个 chunk 最大?
- 这个 chunk 里都塞了什么?
结果非常典型:
- 多个大型第三方库被打进了同一个 chunk
- vendor 和业务代码严重耦合
- Tree Shaking 虽然开了,但无能为力
👉 问题不是依赖多,而是它们被错误地“绑”在了一起。
二、核心思路:把“不该在一起的代码拆开”
这次优化的核心目标只有一个:
让这个 4MB 的 chunk 彻底“解体”
也就是说:
- 大而稳定的依赖 → 单独拆
- 非首屏必需的库 → 延迟加载
- 能 tree shake 的 → 确保真的被 shake 掉
三、manualChunks:直接命中问题核心
我在 vite.config.ts 里针对第三方依赖做了手动拆包:
function manualChunks(id: string) {
if (id.includes("node-forge")) return "node-forge";
if (id.includes("lodash")) return "lodash";
if (id.includes("decimal.js")) return "decimal";
if (id.includes("lexical")) return "lexical";
if (id.includes("refractor")) return "refractor";
if (id.includes("ethers")) return "ethers";
if (id.includes("i18next")) return "i18next";
if (id.includes("axios")) return "axios";
}
为什么这一刀这么有效?
在优化前:
- 这些库全部被打进同一个巨大 chunk
- 任何一个页面加载,都会被迫下载它们
拆完之后:
- 最大 chunk 体积立刻下降
- 各个依赖按需加载
- 浏览器缓存利用率明显提升
👉 这一步是 4MB → 1MB 的关键。
四、防止“拆过头”:experimentalMinChunkSize
拆包之后,我遇到的下一个问题是:
chunk 变多了,但有些只有几 KB
这时候我加了这个配置:
experimentalMinChunkSize: 20 * 1024
它的作用很简单:
小于 20KB 的 chunk,Rollup 会尝试合并
这样可以避免:
- 请求数暴增
- JS 被拆得过碎
这是一个非常工程化、但对实际体验很重要的配置。
五、build 阶段的几个关键瘦身开关
拆包只是结构优化,体积能不能再往下掉,还得靠 build 阶段。
1️⃣ 使用 terser 做最终压缩
minify: "terser"
相比 esbuild:
- terser 更激进
- 最终体积更小
在我这个场景下,对最大 chunk 的体积下降非常明显。
2️⃣ 确保 Tree Shaking 真正生效
treeshake: true
format: "es"
但更重要的是代码写法。
比如 lodash:
// ❌ 整包引入,Tree Shaking 无效
import _ from "lodash";
// ✅ 按需引入
import debounce from "lodash/debounce";
很多时候,Tree Shaking 失败并不是工具问题,而是代码问题。
六、结果:4MB → 500KB 是怎么来的?
简单总结一下这次体积下降的来源:
- ❌ 一个超级大 chunk
- ✅ 多个职责清晰的 chunk
- ❌ vendor + 业务代码强耦合
- ✅ 稳定依赖独立缓存
最终:
最大的 JS Chunk 从 4000+ KB 降到了 500+ KB
而且这个结果是:
- 未压缩体积
- 不依赖玄学配置
- 对业务代码几乎无侵入
七、一些容易被忽略的体积“刺客”
在这次优化过程中,还顺手解决了几个问题:
- lodash 整包引入
- crypto / decimal / forge 这类库首屏加载
- 开发期遗留的调试代码
这些都不会让你“一夜瘦身”, 但会持续让 chunk 变大。
最后
这次优化给我最大的一个感受是:
很多性能问题,并不是因为依赖多,而是因为它们被放错了地方。
如果你项目里也有一个:
- 异常大的 JS 文件
- 怎么压都压不下去的 chunk
不妨先从 “拆” 开始,而不是盲目换工具。