JS `Terser` / `UglifyJS` 压缩原理:AST 转换与代码优化策略

各位靓仔靓女,晚上好!我是今晚的讲师,大家都叫我“代码老中医”。今天咱们聊聊前端性能优化的重要一环——JS 代码压缩,尤其是 Terser/UglifyJS 这两位“压榨”代码的大师。

咳咳,开始之前先声明一下,今天的讲座可能会有点“烧脑”,但保证通俗易懂,绝不让你睡着。咱们的目标是,听完之后,你也能拿起“手术刀”,给自己的 JS 代码做个精简手术。

第一部分:为何要“压榨”JS 代码?

在深入了解 Terser/UglifyJS 之前,咱们先搞清楚一个根本问题:为啥要费劲巴拉地压缩 JS 代码?

想象一下,你家宽带是 100M 的,但你下载一个 1G 的电影,也要等个几分钟。这说明什么?传输也是要花时间的,尤其是对于网络环境复杂的移动端。

JS 代码也一样。你写的代码再漂亮、再优雅,最终都要通过网络传输到用户的浏览器里。体积越大,传输时间越长,用户体验就越差。用户体验差了,老板就皱眉头,你就要加班了。

所以,压缩 JS 代码,本质上就是在优化用户体验,提升网站性能

具体来说,压缩 JS 代码有以下几个好处:

  • 减少文件体积: 这是最直接的好处,文件越小,传输越快。
  • 减少 HTTP 请求: 虽然现在流行 HTTP/2,但减少请求总是好的。压缩后,一些小文件可以直接内联到 HTML 中,减少请求。
  • 提高加载速度: 加载速度快了,用户就能更快地看到页面内容,减少跳出率。
  • 增加安全性: 压缩后的代码可读性差,一定程度上可以防止代码被轻易复制或篡改(当然,这并不是主要目的,混淆才是)。

第二部分:Terser/UglifyJS:代码“减肥”的秘密武器

Terser 和 UglifyJS 都是 JS 代码压缩工具,它们的主要原理都是基于 AST (Abstract Syntax Tree,抽象语法树) 转换和一系列的代码优化策略。

简单来说,它们会把你的代码“吃”进去,然后经过一系列复杂的“消化”过程,最后“吐”出来一份更精简的代码。

那么,它们是如何“消化”代码的呢?主要分为以下几个步骤:

  1. 解析(Parsing): 将 JS 代码解析成 AST。AST 是一种树状结构,用来表示代码的语法结构。
  2. 转换(Transforming): 遍历 AST,应用各种优化规则,对 AST 进行修改。
  3. 生成(Generating): 将修改后的 AST 转换回 JS 代码。

咱们来举个例子,假设有这样一段代码:

function add(a, b) {
  var result = a + b;
  return result;
}

console.log(add(1, 2));

这段代码经过 Terser/UglifyJS 处理后,可能会变成这样:

function add(a,b){return a+b}console.log(add(1,2));

是不是感觉像“整容”了一样?但功能还是一样的。

第三部分:AST:代码的“骨架”

要理解 Terser/UglifyJS 的工作原理,首先要了解 AST。

AST 可以理解为代码的“骨架”,它用一种树状结构来表示代码的语法结构。每个节点代表代码中的一个语法单元,比如变量声明、函数调用、运算符等等。

例如,上面 var result = a + b; 这行代码的 AST 节点可能长这样(简化版):

{
  "type": "VariableDeclaration",
  "declarations": [
    {
      "type": "VariableDeclarator",
      "id": {
        "type": "Identifier",
        "name": "result"
      },
      "init": {
        "type": "BinaryExpression",
        "operator": "+",
        "left": {
          "type": "Identifier",
          "name": "a"
        },
        "right": {
          "type": "Identifier",
          "name": "b"
        }
      }
    }
  ],
  "kind": "var"
}

是不是感觉有点复杂?没关系,你只需要知道 AST 把代码拆解成了一个个小的语法单元,并且用树状结构组织起来。

Terser/UglifyJS 在转换代码时,就是通过遍历 AST,找到需要优化的节点,然后进行修改。

第四部分:Terser/UglifyJS 的“独门秘籍”:代码优化策略

Terser/UglifyJS 之所以能把代码“压榨”得如此精简,靠的是一系列的代码优化策略。这些策略就像它们的“独门秘籍”,让它们在代码“减肥”方面技高一筹。

下面咱们来介绍一些常见的优化策略:

  1. 删除空白和注释: 这是最基本的优化,直接把代码中的空格、换行、注释统统删掉。

    // 原始代码
    function hello(name) {
      // 注释
      console.log("Hello, " + name + "!");
    }
    
    // 压缩后
    function hello(name){console.log("Hello, "+name+"!")}
  2. 变量名混淆(Mangling): 将变量名、函数名替换成更短的名称,比如 abc 等等。

    // 原始代码
    function calculateSum(firstNumber, secondNumber) {
      var sum = firstNumber + secondNumber;
      return sum;
    }
    
    // 压缩后 (混淆变量名)
    function calculateSum(a,b){var c=a+b;return c}

    注意: 混淆变量名可能会导致代码难以调试,所以一般只在生产环境中使用。

  3. 常量折叠(Constant Folding): 将常量表达式计算结果替换成常量值。

    // 原始代码
    var PI = 3.14159;
    var radius = 5;
    var area = PI * radius * radius;
    
    // 压缩后 (常量折叠)
    var PI = 3.14159;
    var radius = 5;
    var area = 78.53975;
  4. 无用代码删除(Dead Code Elimination): 删除永远不会执行的代码,比如 if (false) { ... } 中的代码。

    // 原始代码
    function doSomething() {
      if (false) {
        console.log("This will never be executed.");
      }
      console.log("Hello!");
    }
    
    // 压缩后 (无用代码删除)
    function doSomething(){console.log("Hello!")}
  5. 内联函数(Function Inlining): 将一些简单的函数调用替换成函数体本身。

    // 原始代码
    function square(x) {
      return x * x;
    }
    
    var result = square(5);
    
    // 压缩后 (内联函数)
    var result = 5 * 5;
  6. 简化布尔表达式: 简化 &&||! 等布尔表达式。

    // 原始代码
    var a = true;
    var b = false;
    var c = a && !b;
    
    // 压缩后 (简化布尔表达式)
    var c = true;
  7. 属性重写 (Property rewriting): 将对象属性名替换为更短的字符。

    // 原始代码
    var obj = {
      veryLongPropertyName: 123,
      anotherVeryLongPropertyName: 456
    };
    
    console.log(obj.veryLongPropertyName + obj.anotherVeryLongPropertyName);
    
    // 压缩后 (属性重写)
    var obj = {
      a: 123,
      b: 456
    };
    
    console.log(obj.a + obj.b);

这些只是一些常见的优化策略,Terser/UglifyJS 还有很多其他的优化技巧。

第五部分:Terser vs UglifyJS:谁更胜一筹?

UglifyJS 曾经是 JS 代码压缩领域的王者,但后来因为一些原因停止了维护。Terser 是 UglifyJS 的一个分支,并且一直在积极维护和更新,所以现在 Terser 已经取代了 UglifyJS,成为了更主流的选择。

简单来说,Terser 在以下几个方面优于 UglifyJS:

  • 更好的 ES6+ 支持: Terser 对 ES6+ 的语法支持更好,可以正确处理箭头函数、类、解构赋值等新特性。
  • 更积极的维护: Terser 社区非常活跃,bug 修复和新功能开发都很及时。
  • 更多的配置选项: Terser 提供了更多的配置选项,可以更灵活地控制压缩过程。

第六部分:如何使用 Terser?

Terser 可以通过多种方式使用,比如命令行工具、Node.js API、Webpack 插件等等。

  1. 命令行工具:

    首先,你需要全局安装 Terser:

    npm install -g terser

    然后,就可以在命令行中使用 Terser 了:

    terser input.js -o output.min.js

    这条命令会将 input.js 压缩成 output.min.js

    你还可以通过命令行选项来配置 Terser 的行为,比如:

    terser input.js -o output.min.js -m -c
    • -m:启用变量名混淆。
    • -c:启用代码压缩。
  2. Node.js API:

    你也可以在 Node.js 项目中使用 Terser API:

    const terser = require("terser");
    const fs = require("fs");
    
    const code = fs.readFileSync("input.js", "utf8");
    const result = terser.minify(code);
    
    if (result.error) {
      console.error(result.error);
    } else {
      fs.writeFileSync("output.min.js", result.code);
    }

    这段代码会将 input.js 压缩成 output.min.js

  3. Webpack 插件:

    如果你使用 Webpack 打包项目,可以使用 terser-webpack-plugin 插件来自动压缩 JS 代码:

    首先,安装插件:

    npm install terser-webpack-plugin --save-dev

    然后,在 webpack.config.js 中配置插件:

    const TerserPlugin = require('terser-webpack-plugin');
    
    module.exports = {
      // ...
      optimization: {
        minimizer: [new TerserPlugin()],
      },
    };

    这样,Webpack 在打包时就会自动使用 Terser 压缩 JS 代码。

第七部分:Terser 配置选项:精雕细琢你的代码

Terser 提供了丰富的配置选项,可以让你更精细地控制压缩过程。下面是一些常用的配置选项:

选项 描述
mangle 控制变量名混淆。可以设置为 truefalse,或者一个包含更详细配置的对象。
compress 控制代码压缩。可以设置为 truefalse,或者一个包含更详细配置的对象。
output 控制输出选项,比如是否保留注释、是否美化代码等等。
sourceMap 控制是否生成 Source Map。
keep_fnames 阻止 Terser 删除或混淆函数名称,这对于依赖函数名称的库(例如 Angular)很有用。
module 设置为 true 可以启用 ES 模块特定的优化。
toplevel 设置为 true 可以启用顶级作用域的优化。

例如,你可以这样配置 Terser:

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...
  optimization: {
    minimizer: [
      new TerserPlugin({
        terserOptions: {
          mangle: {
            // 阻止混淆某些变量名
            reserved: ['$', 'webpackJsonp'],
          },
          compress: {
            // 删除 console.log 语句
            drop_console: true,
          },
          output: {
            // 保留注释
            comments: false,
          },
        },
      }),
    ],
  },
};

这个配置会:

  • 混淆变量名,但保留 $webpackJsonp 这两个变量名不被混淆。
  • 删除代码中的 console.log 语句。
  • 删除代码中的注释。

第八部分:注意事项:压缩虽好,也要适度

JS 代码压缩虽然能带来很多好处,但也要注意适度,避免过度压缩导致代码出错或难以调试。

以下是一些需要注意的地方:

  • 不要在开发环境中使用压缩代码: 压缩后的代码可读性差,不利于调试。应该只在生产环境中使用压缩代码。
  • 谨慎使用变量名混淆: 混淆变量名可能会导致代码难以调试,所以一般只在生产环境中使用。
  • 注意兼容性: 不同的浏览器对 JS 语法的支持程度不同,压缩时要注意兼容性,避免使用一些过于激进的优化策略。
  • 测试: 压缩后的代码一定要进行充分的测试,确保功能正常。

第九部分:总结:让你的代码“瘦”下来

JS 代码压缩是前端性能优化的重要一环。通过使用 Terser/UglifyJS 等工具,可以有效地减少 JS 文件体积,提高加载速度,提升用户体验。

今天咱们学习了 Terser/UglifyJS 的基本原理、代码优化策略以及使用方法。希望大家能够学以致用,让自己的代码“瘦”下来,为用户带来更好的体验。

好了,今天的讲座就到这里。谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注