Terser配置实战:前端代码压缩与性能优化技巧

司徒景鑫 优化 阅读 1,610
赞 10 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

上个月接手一个老项目重构,前端是纯 React + Webpack 的 SPA,打包后体积快 3MB 了(gzip 前)。用户反馈首屏加载慢,尤其在低端安卓机上直接卡成 PPT。团队评估后决定先从压缩入手——毕竟代码体积大得离谱,连 devtool 都卡顿。

Terser配置实战:前端代码压缩与性能优化技巧

一开始我直接用 Webpack 默认的 TerserPlugin,没改任何配置。结果发现压缩率一般,而且有些 console.log 居然没删干净(后来才知道默认配置只删 production 模式下的 console)。更糟的是,打包时间暴涨到 2 分钟,CI/CD 流水线天天超时。老板问进度,我只能苦笑:“在等电脑跑完”。

最大的坑:压缩率和构建速度的拉扯

折腾的第一步是调 terserOptions。我照着文档把 drop_console 打开,以为能省不少字节:

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

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
    ],
  },
};

结果呢?console.log 确实没了,但体积只少了 50KB。更离谱的是,构建时间从 90 秒飙到 150 秒。查了下 Terser 文档才发现,compress 开启后会做 AST 优化,对大型项目特别吃 CPU。我本地 16G 内存的 MacBook 都卡,别提 CI 服务器了。

后来想到个土办法:只删 console.log,不跑完整压缩。翻 GitHub 找到社区方案——用正则替换。但 Webpack 官方不推荐,说可能破坏代码。试了两天,果然在某个异步函数里把 console.error 误删了,导致线上错误监控失效。赶紧回滚。

核心代码就这几行

最后妥协方案是分层压缩:开发环境不压缩,测试环境轻量压缩,生产环境全量压缩。关键配置如下:

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

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 多核并行,亲测提速 30%
        terserOptions: {
          parse: {
            // 忽略注释,省点解析时间
            comments: false,
          },
          compress: {
            // 只删 console,不搞复杂优化
            drop_console: true,
            drop_debugger: true,
            // 关键!禁用某些耗时优化
            reduce_vars: false, 
            collapse_vars: false,
          },
          mangle: {
            // 保留关键属性名,避免和第三方库冲突
            reserved: ['$', 'jQuery', 'ga'],
          },
          output: {
            // 删掉所有注释
            comments: false,
          },
        },
        // 跳过已压缩文件(比如 node_modules 里的)
        exclude: /node_modules/,
      }),
    ],
  },
};

这里注意我踩过好几次坑:reduce_varscollapse_vars 看似能提升压缩率,但在我们的代码库里反而引发变量名冲突(特别是用了大量闭包的旧模块)。关掉后构建时间降回 80 秒,体积只比全开大 20KB——完全值得。

另外 exclude: /node_modules/ 是救命稻草。之前没配这个,Terser 对每个 npm 包都重新压缩,光 lodash 就多花 10 秒。现在直接跳过,CI 时间稳定在 60 秒内。

还有个隐藏雷:source map 的锅

有次上线后用户报错,但 Sentry 里堆栈全是 a.b.c.d()。查了半天发现 Terser 默认生成的 source map 不包含原始变量名。赶紧补配置:

new TerserPlugin({
  // ...其他配置
  sourceMap: true, // 必须开
  terserOptions: {
    mangle: {
      keep_fnames: true, // 保留函数名
    },
  },
}),

keep_fnames 会让体积增加约 1.5%。权衡后还是开了——总不能让用户问题没法 debug。不过只在 staging 环境开,生产环境关掉(用 Sentry 的 artifact 上传替代)。

回顾与反思

最终效果:生产包从 2.8MB 降到 2.1MB(gzip 后 650KB → 480KB),首屏加载快了 1.2 秒。构建时间控制在可接受范围,团队不用再干等。

做得好的地方:

  • 分层配置策略,平衡速度和体积
  • 明确禁用高风险压缩选项,避免线上事故
  • 通过 exclude 规避无效计算

不足之处也很明显:

  • 没彻底解决 source map 体积问题,后续考虑用 hidden-source-map + 单独上传
  • 某些动态 require 的模块没被压缩(Webpack 的 splitChunks 配置问题,和 Terser 无关)
  • 其实应该先做代码分割,而不是死磕压缩——但项目 deadline 逼得只能先救火

说到底,Terser 不是银弹。它适合处理“已经写好的代码”,但治本还得靠架构优化。不过对于赶工期的项目,合理配置确实能快速见效。

最后叨叨两句

以上是我踩坑后的总结,希望对你有帮助。如果你也在调 Terser,记住:别盲目开所有压缩选项,先 profile 构建时间;生产环境务必测试 source map 是否可用;console 删除用 drop_console 比正则安全一万倍。

有更优的实现方式欢迎评论区交流——比如你们怎么处理第三方库的重复压缩?或者有没有试过 swc 压缩?最近听说它比 Terser 快 10 倍,但还没敢在生产用……

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

暂无评论