前端打包体积一直降不下来?我靠这几款分析工具把产物体积砍了 60%

前端打包体积一直降不下来?我靠这几款分析工具把产物体积砍了 60%

项目越做越大,打包后的 JS 文件动不动就几百 KB、甚至几 MB。

打开页面白屏好几秒,LCP 标红,用户骂娘,产品经理天天蹲你工位。

我之前也这样。一个后台管理系统,首屏 JS 打包出来 1.8MB——你想想,用户打开一个管理后台,得先等他家浏览器默默下载 1.8MB 的 JS 文件,然后解析、执行,页面才能动弹。这体验谁受得了?

后来我学乖了:不要瞎优化,先拿数据分析。你都不知道哪块大,怎么知道该切哪块?

今天就把我压箱底的 Bundle 分析三板斧分享出来,看完你也能手起刀落。

先装个 X 光机:webpack-bundle-analyzer

如果你还在用 Webpack,那webpack-bundle-analyzer 就是你的第一站。

装它:

npm install --save-dev webpack-bundle-analyzer

配置也非常无脑:

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'server',
      openAnalyzer: true,
      generateStatsFile: true,
      statsFilename: 'stats.json'
    })
  ]
}

npm run build 之后,浏览器会弹出一个交互式的树状图(treemap),每个方块代表一个模块,面积越大体积越大。

我那次 1.8MB 的首屏 JS 一看就知道问题在哪了:

  • antd 完整打包:850KB(实际上我就用了 5 个组件)
  • moment.js:230KB(但项目用的其实是 dayjs)
  • lodash 完整引入:520KB(只用了个 getdebounce
  • 其他冗余代码加在一起:200KB+

你看看,真正属于业务代码的部分,其实还不到 300KB。剩下的全是第三方依赖没做好按需加载。

Vite 项目换 rollup-plugin-visualizer

如果你已经切换到 Vite 了(2026 年了应该没人在新项目里用 Webpack 了吧),对应的分析工具是 rollup-plugin-visualizer

npm install --save-dev rollup-plugin-visualizer

vite.config.ts 里配置:

import { visualizer } from 'rollup-plugin-visualizer'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
      filename: 'stats.html'
    })
  ]
})

它会生成一个可交互的 stats.html,支持 treemap、sunburst、network 三种视图模式。我比较喜欢 sunburst(环形图)——能直观看出依赖层级关系,哪个包是谁引进来的,一目了然。

Vite 用户有个常见误区:觉得 Vite 开发过程爽,打包体积就不用管了。错。Vite 底层用的还是 Rollup,所有 Webpack 的优化经验基本可以平移。

深度分析:source-map-explorer

webpack-bundle-analyzer 能看出大块,但如果你想知道某个大模块里面到底是哪段代码占空间,还得上 source-map-explorer

npm install --save-dev source-map-explorer

配合生成 source-map 来分析:

{
  "scripts": {
    "analyze": "source-map-explorer 'dist/js/*.js'"
  }
}

它会根据 source map 反向映射到原始源码,精确告诉你每个源文件的压缩前/压缩后/ gzip 后的大小。我特别喜欢用它来追那个”看起来没什么但怎么就这么大”的幽灵模块。

最有价值的优化方案

工具上完了,说点实际的。这些是我从多个项目里总结出来的、砍体积最狠的几个招。

1. 按需加载不是配置问题,是习惯问题

很多人的”按需加载”只做了 UI 组件库,但忽略了自定义工具函数库。

// ❌ 这样会全量引入
import { debounce, get, throttle, cloneDeep, uniqBy } from 'lodash'

// ✅ 只引入需要的
import debounce from 'lodash/debounce'
import get from 'lodash/get'

一个朋友的项目,光改 lodash 的引入方式,从全量换成按需,体积直接降了 400KB。

2. 别再用 moment.js 了

我知道,moment.js 的 API 确实好用。但它真的是一个230KB 的胖子

体积(压缩前) gzip
moment.js 230KB 70KB
dayjs 6.5KB 3KB
date-fns 按需约 10KB 4KB
Luxon 60KB 20KB

我个人的选择:普通项目用 dayjs(API 和 moment 几乎一样),复杂国际化需求用 date-fns(函数式按需加载)。

3. Code Splitting 要”智能”

不要只扔一个 React.lazy 就完事。真正的 code splitting 要考虑:

// 好的拆分:按路由 + 按组件权重
const Dashboard = React.lazy(() => import('./pages/Dashboard'))
const Settings = React.lazy(() => import('./pages/Settings'))

// 更细的拆分:把非首屏的大组件单独拆出来
const DataChart = React.lazy(() => import('./components/DataChart'))
const PdfExport = React.lazy(() => import('./components/PdfExport'))

同时配合 Webpack 的 splitChunks

splitChunks: {
  chunks: 'all',
  cacheGroups: {
    vendor: {
      test: /[\/]node_modules[\/]/,
      name: 'vendor',
      chunks: 'all',
      priority: 10
    },
    antd: {
      test: /[\/]node_modules[\/]antd[\/]/,
      name: 'antd',
      chunks: 'all',
      priority: 20
    }
  }
}

4. Tree Shaking 的隐藏开关

很多人以为 Webpack production 模式自动开启 Tree Shaking 就完事了。但其实有个关键配置常被忽略——sideEffects

package.json 里加上:

{
  "sideEffects": ["*.css"]
}

告诉 Webpack:除了 CSS 文件,其他文件都没有副作用,可以放心摇掉。

我还见过一个踩坑案例:某人文件夹里有一个 index.js 只导出了部分函数,但另一个模块引入了整个文件夹:

// ❌
import utils from './utils'

// ✅
import { formatDate } from './utils/formatDate'

前者破坏了 Tree Shaking,后者才能正确摇树。

真实的优化效果

上面说的这些手段,我在两个项目上完整走了一遍:

指标 优化前 优化后 变化
首屏 JS 体积 1.8MB 680KB -62%
LCP 3.8s 1.2s -68%
TTI 4.2s 1.8s -57%

这些数字不是凭空来的。就是从上面说的分析工具入手,按数据说话,一步步砍出来的。

总结一下

不拿到分析报告就不要谈优化。拍脑袋说”这个包太大了”和真的看数据去砍,差别就是 10% 和 60% 的差距。

工具没多神秘:webpack-bundle-analyzer(Webpack)、rollup-plugin-visualizer(Vite)、source-map-explorer(深追)。建个分析脚本,每天构建完看一眼,把体积变化纳入 code review,项目就不会偷偷胖起来。

你的项目打包后首屏 JS 多大?去查查,跟我说说结果。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单