3、构建阶段优化(下):压缩与混淆、Gzip/Brotli压缩、CDN加速与外部化

好,咱们接着聊构建阶段的优化。上一节我们讲了代码分割和Tree Shaking,这次要聊的这几个东西,说白了就是给项目「瘦身」和「提速」的最后一公里。

我个人习惯把这一节的内容分成三块:代码压缩与混淆传输压缩依赖外部化。这三板斧砍下去,你的打包体积至少能再缩个30%-50%。

一、压缩与混淆:让代码变得「面目全非」

先说说压缩。你写出来的代码,变量名又长又清晰,注释一堆,空格换行整整齐齐——这些对浏览器来说都是多余的。压缩的目的,就是把这些没用的东西全干掉。

1. TerserPlugin:JavaScript的终极瘦身师

Vue CLI和Vite默认都用TerserPlugin来做JS压缩。它干的事情很简单:去掉注释、缩短变量名、删除死代码

我在项目中遇到过一个问题:默认配置下,Terser会把console.log也删掉。这对线上环境是好事,但开发阶段调试就麻烦了。所以我的习惯是区分环境配置:

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

module.exports = {
  configureWebpack: {
    optimization: {
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            compress: {
              drop_console: process.env.NODE_ENV === 'production', // 生产环境干掉console
              drop_debugger: true,
            },
            output: {
              comments: false, // 去掉注释
            },
          },
          extractComments: false, // 不提取注释到单独文件
        }),
      ],
    },
  },
};

嗯,这里要注意一点:drop_console 是暴力删除,如果你有些console是故意留的(比如埋点日志),那就别开这个选项。我曾经有个同事,开了这个选项后,线上埋点全挂了,排查了半天才发现是压缩给干掉了。

避坑指南: 我曾经在生产环境开启 drop_console,结果把第三方SDK的日志也删了,导致数据上报异常。建议只删除 console.log,保留 console.warnconsole.error

2. CSS Minimizer:给样式表减减肥

CSS压缩相对简单,主要就是去掉空格、换行、注释,合并相同的选择器。我用的是 CssMinimizerPlugin,它和TerserPlugin配合起来效果很好。

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  optimization: {
    minimizer: [
      new CssMinimizerPlugin({
        minimizerOptions: {
          preset: [
            'default',
            {
              discardComments: { removeAll: true }, // 干掉所有注释
              normalizeWhitespace: true, // 压缩空白
            },
          ],
        },
      }),
    ],
  },
};

你想想看,一个项目里CSS文件动不动几百KB,压缩后能省下30%的体积。虽然不如JS压缩效果那么明显,但蚊子腿也是肉啊。

小技巧: 如果你用了Tailwind CSS,压缩效果会更明显。因为Tailwind生成的类名特别多,压缩后能去掉大量冗余空格。

二、Gzip/Brotli压缩:传输层的加速器

代码压缩完了,但文件还是要通过网络传输。这时候就需要传输层的压缩了。说白了,就是在服务器把文件再压一遍,浏览器收到后解压。

目前主流的有两种算法:GzipBrotli。Brotli是Google推出的新算法,压缩率比Gzip高20%-30%。我个人强烈建议用Brotli,前提是你的服务器和浏览器都支持。

1. 在构建时生成预压缩文件

我习惯在构建阶段直接生成 .gz.br 文件,这样服务器就不用实时压缩了,能省下CPU资源。

// 使用 compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin');

module.exports = {
  plugins: [
    new CompressionPlugin({
      filename: '[path][base].gz', // 生成 .gz 文件
      algorithm: 'gzip',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240, // 只处理大于10KB的文件
      minRatio: 0.8, // 压缩率低于0.8才压缩
    }),
    new CompressionPlugin({
      filename: '[path][base].br', // 生成 .br 文件
      algorithm: 'brotliCompress',
      test: /\.(js|css|html|svg)$/,
      threshold: 10240,
      minRatio: 0.8,
    }),
  ],
};

我在项目中遇到过一个问题:生成预压缩文件后,Nginx没配置好,导致浏览器请求的是 .br 文件,但服务器返回的是 .gz 文件,结果解压失败。嗯,这里要注意服务器配置要匹配。

2. Nginx配置示例

# 开启静态压缩文件支持
location ~* \.(js|css|html|svg)$ {
    gzip_static on;          # 优先使用预压缩的 .gz 文件
    brotli_static on;        # 优先使用预压缩的 .br 文件
    
    # 如果浏览器支持 Brotli,返回 .br 文件
    # 否则返回 .gz 文件
    add_header Content-Encoding gzip;
}
核心结论: Brotli 压缩率比 Gzip 高 20%-30%,但压缩速度慢。建议在构建时预生成,不要用实时压缩。如果服务器不支持 Brotli,退而求其次用 Gzip 也行。

三、CDN加速与外部化:把包袱甩给CDN

最后聊一个我特别喜欢的优化手段:Externals。说白了,就是把那些不常变的第三方库(比如Vue、React、Lodash)从打包文件里踢出去,改用CDN加载。

为什么要这么做?你想想看,用户访问你的网站,如果他从另一个网站已经加载过Vue了,那浏览器缓存里就有。你再用CDN加载,大概率命中缓存,连网络请求都省了。

1. 配置Externals

// vue.config.js
module.exports = {
  configureWebpack: {
    externals: {
      vue: 'Vue',
      'vue-router': 'VueRouter',
      vuex: 'Vuex',
      axios: 'axios',
    },
  },
};

配置完这个,打包后的文件里就不会包含这些库了。体积能小一大截。我有个项目,把Vue全家桶外部化后,打包体积从800KB降到了200KB。

2. 在HTML中引入CDN

然后在 public/index.html 里手动引入CDN链接:

<!-- 使用 CDN 加载 Vue 全家桶 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.0/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4.0.0/dist/vue-router.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@4.0.0/dist/vuex.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.21.0/dist/axios.min.js"></script>
避坑指南: 我曾经把Element UI也外部化了,结果发现它依赖Vue的全局实例,而CDN加载的Vue版本和本地版本不一致,导致组件渲染异常。建议只外部化那些不依赖其他库的纯工具库。

3. 选择合适的CDN

CDN的选择也有讲究。我个人习惯用 jsDelivrcdnjs,它们有全球节点,速度稳定。如果是国内项目,建议用 BootCDN七牛云

CDN服务商 特点 适用场景
jsDelivr 全球节点,支持npm包 国际项目
cdnjs 老牌CDN,库全 通用场景
BootCDN 国内节点,速度快 国内项目
七牛云 企业级,可自定义 大型项目

四、总结一下

这一节的内容,说白了就是三件事:

  • 压缩与混淆:用TerserPlugin和CssMinimizerPlugin把代码体积压到最小
  • 传输压缩:用Gzip或Brotli在传输层再压一遍,优先用Brotli
  • CDN外部化:把第三方库甩给CDN,利用浏览器缓存减少请求

这三步走完,你的项目加载速度至少能快一倍。我有个项目,优化前首屏加载要3秒,优化后直接降到1秒以内。嗯,效果就是这么明显。

下一节我们聊聊运行时优化,那才是真正考验功力的地方。