如何做前端包体积分析?发现 bundle 过大应该怎么优化?
简化版
包体积分析要先看构建产物组成、gzip/brotli 后大小、首屏 chunk、重复依赖和第三方库占比,再按证据优化:按路由分包、替换重型库、开启 Tree Shaking、减少重复依赖、压缩资源和调整缓存策略。
详细版
不要只看源代码行数,也不要只看未压缩体积。真实体验更关心首屏必须下载、解析和执行的 JavaScript,以及 CSS、字体、图片等关键资源。
常见工具包括 webpack-bundle-analyzer、rollup-plugin-visualizer、source-map-explorer 和构建工具自带 stats。分析后先定位最大 chunk、重复包、moment/lodash/图表库等重型依赖,再评估是否能懒加载、按需引入、替换或外置。
优化要有指标闭环:构建前后比较 initial JS、async JS、gzip/brotli、LCP、INP、错误率和缓存命中率,不能只追求产物数字变小。
完整版教学
一、先明确分析对象不是“总代码量”
前端包体积问题真正影响用户的是关键路径资源。一个项目总产物 5 MB 不一定首屏慢,如果首屏只加载 180 KB;一个项目总产物 900 KB 也可能慢,如果 900 KB 全在入口 chunk。
因此分析时至少拆成 4 个口径:未压缩体积、gzip/brotli 后体积、首屏同步资源体积、运行时解析执行成本。JavaScript 的成本不仅是下载,还包括解析、编译和执行。
记忆钩子:包体分析先看“首屏必须付出的代价”,再看“全站总资源”。
二、从构建报告里找到最大块和重复依赖
包体分析工具通常会把 chunk、模块和依赖大小可视化。第一步不是立刻优化,而是列出 Top 10 体积来源。
dist/assets/index.js 420 KB gzip -> 首屏入口
dist/assets/vendor.js 310 KB gzip -> React + UI + 图表
dist/assets/editor.js 280 KB gzip -> 富文本编辑器,非首屏
dist/assets/report.js 190 KB gzip -> 报表页异步块
如果 Top 1 是入口 chunk,就要优先拆;如果 Top 1 是非首屏异步 chunk,问题可能没那么紧急。分析要结合加载路径,不然会把精力花在用户暂时不会下载的资源上。
三、第三方库通常是最大收益区
业务代码当然要优化,但很多项目最大块来自第三方库。图表、富文本、日期、地图、低代码编辑器、Excel 导入导出库都可能非常重。
| 依赖类型 | 常见问题 | 优化思路 |
|---|---|---|
| 日期库 | 全量 locale | 按需 locale 或换轻量库 |
| 工具库 | 全量导入 | 按函数导入、ESM 版本 |
| 图表库 | 首屏同步加载 | 路由或组件懒加载 |
| 编辑器 | 只少数页面使用 | 独立异步 chunk |
| UI 库图标 | 全量图标进入包 | 按需图标 |
假设首屏 gzip 体积 620 KB,其中图表库占 180 KB,但首页只展示一个静态数字卡片。把图表库延后到报表页加载后,首屏 JS 可以下降约 29%。这类优化比微调业务代码更有效。
四、分包优化要避免把问题切得更碎
代码分割能减少首屏体积,但不是 chunk 越多越好。过多小 chunk 会增加请求调度、缓存碎片和加载瀑布,尤其在复杂路由嵌套和移动网络下明显。
错误倾向:一个组件一个 chunk,首屏触发 40 个异步请求
合理倾向:按路由、业务域、重型组件切分
优化分包要看请求瀑布。若从 1 个 500 KB chunk 拆成 25 个 20 KB chunk,但首屏仍要全部请求,实际不一定更快。比较时要看总下载、并发限制、主线程执行和缓存命中。
五、Tree Shaking 失效要看模块格式和副作用
已有 Tree Shaking 题讲原理,这里重点放在分析场景。看到某个库“明明只用一个函数却打进很多代码”,要检查它是否提供 ESM、是否被 CommonJS 包装、是否声明 sideEffects、是否被 Babel 转成 CommonJS。
// 风险较高:可能引入整个库
import _ from 'lodash';
// 更利于按需
import debounce from 'lodash/debounce';
现代构建工具也不是万能的。只要模块顶层有不可证明安全的副作用,工具就可能保留代码。包体分析要结合构建配置和依赖源码,而不是简单责怪工具。
六、资源体积也要纳入工程化治理
包体积不只 JavaScript。字体、图片、CSS、wasm、worker 文件也会影响加载。很多团队优化 JS 半天,结果首页最大资源是一张 1.8 MB 的背景图。
| 资源 | 常见优化 |
|---|---|
| 图片 | WebP/AVIF、尺寸裁剪、懒加载 |
| 字体 | 子集化、font-display、预加载关键字体 |
| CSS | 移除未用样式、关键 CSS、避免巨型全局样式 |
| Worker | 单独分包、按需加载 |
| Source Map | 生产不公开或受控上传 |
如果字体文件从 900 KB 子集化到 120 KB,收益可能比删除 10 KB 业务 JS 大得多。工程化负责人要看完整资源账单。
七、建立预算和 CI 卡口
一次优化之后,如果没有预算,很快会反弹。可以设置 bundle budget,比如首屏 JS gzip 不超过 250 KB、单个异步 chunk 不超过 300 KB、图片资源必须走压缩流程。
initial-js-gzip <= 250 KB
largest-async-js-gzip <= 300 KB
new-dependency-size-delta <= 30 KB
CI 卡口不要一开始过严,否则团队会绕开。可以先只报警,再对核心应用设硬阈值。每次新增大型依赖,都要求在 PR 里解释加载路径和替代方案。
八、常见误区与追问
- 误区:bundle 总体积越小,首屏一定越快。 首屏同步资源和主线程执行成本更关键。
- 误区:把所有东西都懒加载就最好。 过度切分会产生请求瀑布和体验抖动。
- 误区:只优化 JS 就够了。 图片、字体、CSS 和 Source Map 管理也会显著影响加载。
- 追问:如何发现重复依赖? 看构建报告中同一库多个版本、多个 chunk 重复包含和 lockfile 版本分裂。
- 追问:新增大型库怎么评审? 看 gzip/brotli 增量、是否首屏加载、能否按需、是否有轻量替代。
- 追问:包体优化如何闭环? 对比优化前后的产物体积、Web 指标、错误率和缓存命中率。
九、加强记忆
包体分析按“口径、定位、优化、卡口”走:先分清未压缩、压缩后、首屏和异步资源;再用报告找最大块、重复依赖和重型库;优化时按路由分包、按需引入、替换重库、治理资源;最后用预算和 CI 防止反弹。