Skip to content

代码压缩

原理

更精简”意味着可以适当 —— 甚至完全牺牲可读性、语义、优雅度而力求用最少字符数的方式书写代码。比如说 const name = 'tecvan';,这个看起来非常简单的赋值语句就有不少可以精简的字符:

  1. 变量名 name 语义很明确,大多数“人”看到就基本明白是干什么用的,但这对计算机并没有什么意义,我们完全可以将 name 修改为 a —— 从 4 个字符精简为 1 个字符,但仍保持改动前后逻辑、功能效果完全一致;
  2. 赋值操作符 = 前后都有空格,这种格式对阅读代码的“人”很友好,视觉效果非常舒适、整齐,但对计算机而言同样毫无意义,我们可以将这前后两个空格删掉 —— 精简了两个字符;
  3. 虽然 constlet 关键词的功能不同,但特定情况下我们同样能牺牲一部分功能性,用 let 替换 const,从 5 个字符精简为 1 个字符。

经过上面三个步骤之后,代码从 const name = 'tecvan'; —— 22 个字符,精简为 let a='tecvan'; —— 18 个字符,往大了说是节省了 18% 的代码体积。其它语言的代码压缩规则也基本都是按照上面这种套路实现的。

我们可以先将字符串形态的代码转换为结构化、容易分析处理的 AST(抽象语法树)形态,之后在 AST 上应用上面的规则做各种语法、语义、逻辑推理与简化替换,最后按精简过的 AST 生成结果代码。

JS压缩:TerserWebpackPlugin

Terser 是当下 最为流行 的 ES6 代码压缩工具之一,支持 Dead-Code Eliminate、删除注释、删除空格、代码合并、变量名简化等等一系列代码压缩功能。Terser 的前身是大名鼎鼎的 UglifyJS,它在 UglifyJS 基础上增加了 ES6 语法支持,并重构代码解析、压缩算法,使得执行效率与压缩率都有较大提升:

Webpack5.0 后默认使用 Terser 作为 JavaScript 代码压缩器,简单用法只需通过 optimization.minimize 配置项开启压缩功能即可:

js
module.exports = {
  //...
  optimization: {
    minimize: true
  }
};

提示:使用 mode = 'production' 启动生产模式构建时,默认也会开启 Terser 压缩。

可以手动创建 terser-webpack-plugin 实例并传入压缩配置实现更精细的压缩功能

js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          compress: {
            reduce_vars: true,
            pure_funcs: ["console.log"],
          },
          // ...
        },
      }),
    ],
  },
};

terser-webpack-plugin 还提供下述配置项

  • test:只有命中该配置的产物路径才会执行压缩
  • include:在该范围内的产物才会执行压缩
  • exclude:与 include 相反,不在该范围内的产物才会执行压缩
  • parallel:是否启动并行压缩,默认值为 true,此时会按 os.cpus().length - 1 启动若干进程并发执行
  • minify:用于配置压缩器,支持传入自定义压缩函数,也支持 swc/esbuild/uglifyjs 等值
  • terserOptions:传入 minify —— “压缩器”函数的配置参数
  • extractComments:是否将代码中的备注抽取为单独文件,可配合特殊备注如 @license 使用

配置项总结下来有两个值得关注的逻辑:

  1. 可以通过 test/include/exclude 过滤插件的执行范围,这个功能配合 minimizer 的数组特性,可以实现针对不同产物执行不同的压缩策略
js
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        test: /foo\.js$/i,
        extractComments: "all",
      }),
      new TerserPlugin({
        test: /bar\.js/,
        extractComments: false,
      }),
    ],
  },
};
  1. 它是一个代码压缩功能骨架,底层还支持使用 SWC、UglifyJS、ESBuild 作为压缩器,使用时只需要通过 minify 参数切换即可,例如:
js
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        minify: TerserPlugin.swcMinify,
        // `terserOptions` 将被传递到 `swc` (`@swc/core`) 工具
        // 具体配置参数可参考:https://swc.rs/docs/config-js-minify
        terserOptions: {},
      }),
    ],
  },
};

提示:TerserPlugin 内置如下压缩器:

  • TerserPlugin.terserMinify:依赖于 terser 库;
  • TerserPlugin.uglifyJsMinify:依赖于 uglify-js,需要手动安装 yarn add -D uglify-js
  • TerserPlugin.swcMinify:依赖于 @swc/core,需要手动安装 yarn add -D @swc/core
  • TerserPlugin.esbuildMinify:依赖于 esbuild,需要手动安装 yarn add -D esbuild

另外,terserOptions 配置也不仅仅专供 terser 使用,而是会透传给具体的 minifier,因此使用不同压缩器时支持的配置选项也会不同。

CSS 压缩:CssMinimizerWebpackPlugin

使用 cssnano 压缩后,大致可以:

  1. 删除注释
  2. 删除重复代码
  3. 合并成一行
  4. 简写,例如margin: 10px 20px 10px 20px; 简写为margin: 10px 20px;
  5. 等等

使用:

  1. 安装依赖
shell
npm i -D css-minimizer-webpack-plugin
  1. 修改 Webpack 配置:
js
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /.css$/,
        // 注意,这里用的是 `MiniCssExtractPlugin.loader` 而不是 `style-loader`
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  optimization: {
    minimize: true,
    minimizer: [
      // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
      "...",
      new CssMinimizerPlugin(),
    ],
  },
  // 需要使用 `mini-css-extract-plugin` 将 CSS 代码抽取为单独文件
  // 才能命中 `css-minimizer-webpack-plugin` 默认的 `test` 规则
  plugins: [new MiniCssExtractPlugin()],
};

HTML 压缩:HtmlMinifierTerser

支持一系列压缩特性:

  • collapseWhitespace:删除节点间的空字符串
  • removeComments:删除备注
  • collapseBooleanAttributes:删除 HTML 的 Boolean 属性值
  • 等等

使用

  1. 安装依赖
shell
npm i -D html-minimizer-webpack-plugin
  1. 修改 Webpack 配置
js
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");

module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
      "...",
      new HtmlMinimizerPlugin({
        minimizerOptions: {
          // 折叠 Boolean 型属性
          collapseBooleanAttributes: true,
          // 使用精简 `doctype` 定义
          useShortDoctype: true,
          // ...
        },
      }),
    ],
  },
  plugins: [
    // 简单起见,这里我们使用 `html-webpack-plugin` 自动生成 HTML 演示文件
    new HtmlWebpackPlugin({
      templateContent: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>webpack App</title>
      </head>
      <body>
        <input readonly="readonly"/>
        <!-- comments -->
        <script src="index_bundle.js"></script>
      </body>
    </html>`,
    }),
  ],
};