前端构建产物分析:如何分析打包体积并定位冗余依赖

域名注册
域名注册 正式会员超兽战士 👑年卡会员
发布于 2026-10-07 17:40 ·0 浏览 ·0 回复

结论:分析打包体积只需四步——先看 gzip/Brotli 压缩后的传输体积,用可视化工具把体积定位到具体模块,用依赖树命令揪出重复安装的同名包,最后按「按需引入 / 换库 / 代码分割 / 动态加载」修掉,并在 CI 里用 size-limit 之类工具卡住体积回归。只看 min 体积、不看压缩后体积,是最常见的误判来源。

分析打包体积,第一步该看哪个数字?

结论:判断一个包值不值得留,看 gzip 后体积,不看源码行数也不看 min 体积。

浏览器实际下载的是压缩后产物,所以 gzip(或 Brotli)体积才等于用户的流量成本。经验值是 Brotli 比 gzip 再小 15%~20%,只要服务端和 CDN 支持,优先开 Brotli。同一份产物,min 体积 290 KB 的库 gzip 后可能只有 70 KB,只看 min 数字会做出错误决策。

具体做法:构建完先扫一眼产物目录,dist/assets/*.js 里超过 200 KB(gzip 前)的 chunk 都值得点开看看。Vite 用户直接跑 npx vite-bundle-visualizer,Next.js 用户装 @next/bundle-analyzer 后执行 ANALYZE=true next build。

打包体积怎么可视化?四个命令直接抄

结论:不同构建器对应不同分析插件,命令都是现成的,不需要改业务代码。

  • Webpack:npm i -D webpack-bundle-analyzer,然后 webpack --profile --json > stats.json 再 npx webpack-bundle-analyzer stats.json,会起一个本地 8888 端口的 treemap 页面,方块面积就是模块体积,点进去能看到是哪一层依赖把它带进来的。
  • Vite / Rollup:rollup-plugin-visualizer 加进 vite.config.ts,配置 visualizer({ gzipSize: true, brotliSize: true, template: 'treemap' }),构建后自动生成 stats.html。
  • 已有 sourcemap 的产物:npx source-map-explorer 'dist/**/*.js',不用改构建配置,直接反查每个文件里各模块的真实占比,特别适合排查「不知道体积从哪来」的存量项目。
  • esbuild:esbuild src/index.ts --bundle --minify --metafile=meta.json --analyze,--analyze 直接输出占用最大的模块列表。

怎么定位冗余依赖和重复打包?

结论:冗余依赖分两类——同名包被装了多个版本,以及同一个库被整包引入却只用了两三个函数。

第一类用依赖树命令查:pnpm 项目跑 pnpm why lodash,npm 项目跑 npm ls lodash,yarn 用 yarn why lodash。如果输出里出现 lodash@4.17.20 和 lodash@4.17.21 两条路径,说明两个版本都被打进了产物。Webpack 项目还可以加 duplicate-package-checker-webpack-plugin,构建时直接把重复包警告打出来。

第二类看可视化图最直接:树图里某个库占了一大块,但你在代码里只 import 了它的一两个方法,就是整包引入。典型例子是 import _ from 'lodash'(完整版 min 约 71 KB)和 import moment from 'moment'(min 约 290 KB、gzip 约 70 KB,还没算 locale)。换成 lodash-es 按需导入、或者干脆换 dayjs(min 约 6.5 KB、gzip 约 2.9 KB),体积差就是几百 KB 级别。

定位到问题后,怎么把体积降下来?

结论:按「按需引入 → 换轻量替代 → 代码分割 → 动态加载」的顺序改,性价比从高到低。

  1. 按需引入:组件库走 ESM 的具名导入,例如 import { Button } from 'antd' 而不是 import antd from 'antd';确认 package.json 的 sideEffects 字段没有误标成 false 之外的值,否则 tree-shaking 会失效;Babel 用户检查 @babel/preset-env 的 modules: false,让打包器能静态分析 ESM。
  2. 统一重复版本:在 pnpm 里用 pnpm.overrides,npm 8+ 用 overrides,yarn 用 resolutions,把子依赖里的旧版本强制对齐到根版本。改完务必重新跑一遍可视化,确认重复块消失。
  3. 代码分割:Webpack 的 splitChunks 或 Vite 的 build.rollupOptions.output.manualChunks,把 React、图表库这类长期不变的依赖单独拆 chunk,利用浏览器长缓存。注意:拆包不减少总体积,只优化首屏加载,别把它当成减体积手段。
  4. 动态加载:路由级 import()、弹窗里的富文本编辑器、只在点击后才用到的图表库,全部改成懒加载,首屏体积能直接掉一大块。

怎么防止体积反弹?

结论:把体积门禁放进 CI,比事后排查便宜得多。

用 size-limit:npm i -D size-limit @size-limit/preset-app,在 package.json 里配 "size-limit": [{ "path": "dist/assets/*.js", "limit": "170 kB" }],CI 里跑 npx size-limit,超限直接让流水线失败。阈值建议按当前实际 gzip 体积上浮 5%~10% 来定,定得太松等于没有门禁。

另外把「禁止整包引入」写进 ESLint,用 eslint-plugin-import 的 no-restricted-imports 拦住 import _ from 'lodash' 这类写法,比靠人自觉可靠。

总结:分析打包体积的核心动作是「先看 gzip、再可视化定位、再用 why 查重复、再按四招修」,最后用 size-limit 在 CI 上锁住。工具链就那几个命令,真正花时间的是判断哪个大块头值得动——优先处理 gzip 后超过 30 KB 且首屏就会加载的模块。

版权声明:本文来自 GJ站长论坛《前端构建产物分析:如何分析打包体积并定位冗余依赖》
原文链接:https://www.gj0.com/thread-445.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~