前端打包优化实战:5个有效减小bundle体积的技巧

Prog.柯福 框架 阅读 2,168
赞 11 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

打包优化这事儿,说大不大,说小不小。项目一上规模,首屏加载慢得像卡碟,用户直接关页面走人。我之前接手一个老项目,初始包快 3MB,gzip 后还有 800KB+,光 vendor 就占了一半。折腾了几天,最后压到 400KB 左右,Lighthouse 分数从 40 多拉到 85+。这里分享几个我反复验证过、在多个项目里跑通的实操方案。

前端打包优化实战:5个有效减小bundle体积的技巧

首先,别迷信 SplitChunks 的默认配置。很多人直接用 Webpack 默认的 splitChunks.cacheGroups.vendor,结果把所有 node_modules 打成一个 chunk。看起来很整洁,但问题很大:只要更新任意一个依赖,整个 vendor 包就失效,缓存全废。我吃过这个亏。

我的做法是按需拆分高频、稳定的第三方库,比如 React、Lodash、Moment.js(虽然现在不推荐用了,但老项目逃不掉)。关键点是用 name + test 组合固定 chunk 名,这样哈希稳定,长期缓存才有效:

// webpack.config.js
optimization: {
  splitChunks: {
    cacheGroups: {
      react: {
        test: /[\/]node_modules[\/](react|react-dom)[\/]/,
        name: 'vendor-react',
        chunks: 'all',
        enforce: true,
      },
      lodash: {
        test: /[\/]node_modules[\/](lodash)[\/]/,
        name: 'vendor-lodash',
        chunks: 'all',
        enforce: true,
      },
      // 其他稳定库同理
    }
  }
}

这样拆完,React 更新不会影响 Lodash 的缓存。而且小 chunk 并行加载更快,尤其对 HTTP/1.1 友好(虽然现在基本都是 HTTP/2 了,但兼容性不能忽视)。

这几种错误写法,别再踩坑了

我在 review 别人代码时,经常看到下面这些“看似聪明实则埋雷”的操作:

  • 滥用动态 import 导致过度拆包:有人为了“按需加载”,把每个组件都写成 () => import('./Component')。结果首屏要加载十几个小 chunk,HTTP 请求爆炸,反而更慢。记住:动态 import 不是银弹,只对非首屏、低频使用的模块有效。首页核心组件该合并还得合并。
  • 忽略 sideEffects 配置:很多库没标 sideEffects: false,Webpack 就不敢 Tree Shaking。比如你只用了 Lodash 的 get,结果整个 Lodash 打进去了。解决方法有两个:一是手动指定路径 import get from 'lodash/get';二是自己加 resolve.alias 强制指向子路径:
    // webpack.config.js
    resolve: {
      alias: {
        'lodash': 'lodash-es', // 或直接指向具体模块
        'moment$': 'moment/moment.js' // 避免加载 locale
      }
    }
  • Source Map 全开上线:开发时为了调试方便开了 sourceMap: true,结果忘了关,生产环境多出几百 KB 的 .map 文件。建议只在 staging 环境开,生产环境关掉,或者用 hidden-source-map(只生成 map 文件但不引用,方便错误追踪)。

实际项目中的坑

有一次我优化一个后台管理系统,发现一个诡异现象:明明拆了 chunk,但 vendor 包还是巨大。查了半天,原来是某个内部 UI 库被当成 node_modules 打进去了——因为它放在 src/components/ui,但 package.json 里写了 "name": "@company/ui",Webpack 误判为外部依赖。解决方法是在 splitChunks 的 test 里排除自己的源码目录:

test: /[\/]node_modules[\/](?!@company[\/])/

另外,别忘了压缩 CSS 和图片。很多人只关注 JS,其实 CSS 未压缩能差 30%~50% 体积。我习惯用 css-minimizer-webpack-plugin + mini-css-extract-plugin,图片用 image-minimizer-webpack-plugin 跑 sharp 压缩。配置很简单:

// webpack.config.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');

optimization: {
  minimizer: [
    '...', // 保留 JS 默认压缩器
    new CssMinimizerPlugin(),
    new ImageMinimizerPlugin({
      minimizer: {
        implementation: ImageMinimizerPlugin.sharpMinify,
        options: {
          encodeOptions: {
            jpeg: { quality: 75 },
            png: { quality: 80 },
          },
        },
      },
    }),
  ],
}

还有一点容易忽略:检查 Polyfill 是否冗余。如果你用 Babel + core-js,可能引入了大量浏览器已原生支持的 polyfill。建议配合 browserslist 配置,或者直接用 @babel/preset-envuseBuiltIns: 'usage' 按需注入。不过注意,usage 模式会增加构建时间,大型项目慎用。

核心代码就这几行

最后贴一个我常用的完整 optimization 配置骨架,删掉业务相关逻辑后可以直接复用:

// webpack.prod.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true, // 生产环境干掉 console
            drop_debugger: true,
          },
        },
      }),
      // CSS 和图片压缩器如上
    ],
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        defaultVendors: false, // 关掉默认 vendor
        vendorReact: {
          test: /[\/]node_modules[\/](react|react-dom|scheduler|react-router)/,
          name: 'vendor-react',
          chunks: 'all',
          enforce: true,
        },
        vendorUtils: {
          test: /[\/]node_modules[\/](lodash|dayjs|axios)/,
          name: 'vendor-utils',
          chunks: 'all',
          enforce: true,
        },
        default: {
          minChunks: 2,
          reuseExistingChunk: true,
        },
      },
    },
    runtimeChunk: 'single', // 把 webpack runtime 单独抽离,避免 vendor 变动
  },
};

注意 runtimeChunk: 'single' 这一行很重要。它把 Webpack 的模块加载逻辑抽成独立 chunk,这样即使业务代码变了,runtime 不变,vendor 的缓存依然有效。

踩坑提醒:这三点一定注意

  • 别盲目追求最小体积:有时候拆得太细,HTTP 请求增多反而拖慢加载。特别是移动端网络,延迟比带宽更致命。建议用 Webpack Bundle Analyzer 看图说话,找到平衡点。
  • 测试真实设备:本地 dev server 快不代表线上快。一定要用 Chrome DevTools 的 Network Throttling 模拟 3G/4G,或者直接手机连热点测。
  • 监控 bundle size 变化:在 CI 里加个脚本,如果主包体积增长超过 10%,自动 fail。我用过 webpack-bundle-analyzer --json 生成报告,配合自定义脚本做对比,效果不错。

以上是我踩坑后的总结,希望对你有帮助。打包优化没有银弹,得结合项目实际反复调。改完后仍有一两个小问题也正常,比如某些老 IE 兼容性问题,但只要核心体验提升,就值得。有更好的方案欢迎评论区交流!

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论