代码的“健身教练” Terser

82 阅读2分钟

一、什么是 Terser?

Terser 像是代码的“健身教练”,专门帮 JS 减肥。它从 UglifyJS 演化而来(UglifyJS 的现代版),支持 ES6+ 语法,能在压缩的同时保持代码功能不变。Webpack 用它来处理生产环境的 JS 文件,去掉注释、缩短变量名、优化表达式,最终输出一个精简版

二、Terser 的核心原理

Terser 的工作可以分成几个步骤,简单来说就是“分析 → 转换 → 输出”:

1、解析代码(Parse)

把 JS 代码转成 抽象语法树(AST) ,就像把文章拆成词和句子

比如:

// 这是一个函数
function add(a, b) {
  return a + b;
}

转换成的 AST 大概是以下

{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "add"
      },
      "params": [
        {
          "type": "Identifier",
          "name": "a"
        },
        {
          "type": "Identifier",
          "name": "b"
        }
      ],
      "body": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "ReturnStatement",
            "argument": {
              "type": "BinaryExpression",
              "operator": "+",
              "left": {
                "type": "Identifier",
                "name": "a"
              },
              "right": {
                "type": "Identifier",
                "name": "b"
              }
            }
          }
        ]
      }
    }
  ],
  "sourceType": "script"
}

图解:

2、压缩与优化

  1. 去掉无用东西:注释、空格、换行全删

    上面的代码变成:

       function add(a,b){return a+b}
    
  2. 缩短名字:把变量名、函数名改短,不影响逻辑

    可能变成:function a(b,c){return b+c}

  3. 优化逻辑:合并表达式、删死代码(dead code)

if (true) { console.log('hi'); } else { console.log('no'); }

// 优化成
console.log('hi')

3、生成代码

  把 AST 转回 JS 字符串,输出最终文件

  加个 Source Map(可选),方便调试时映射回原始代码

Terser 从 AST 生成 JS 字符串时,加入 Source Map 是个“边走边记”的过程

Terser 用一个叫 SourceMapGenerator 的工具(基于 Mozilla 的 source-map 库),在生成压缩代码的同时,记录每个字符的原始位置

// 原始代码
function add(a, b) { return a + b; }
// 压缩后:
function a(b,c){return b+c}
{
  "version": 3,
  "sources": ["input.js"],
  "names": ["add", "a", "b"],
  // mappings 核心字段,用 VLQ 编码表示压缩代码和原始代码的对应位置
  "mappings": "AAAA,QAASA,IAAMC,EAAEC,GACf,OAAOA,EAAEC"
}

  Source Map 原理细节

  Source Map 的本质:它是个查找表,记录了压缩代码的每个字符(或 token)在原始代码里的位置。

  VLQ 编码:为了节省空间,映射信息用 base64 VLQ 压缩,比如 AAAA 表示“第 1 行第 1 列偏移 0”。

  Terser 的实现

  用 source-map 库的 SourceMapGenerator 类。

  每次生成一个 token(比如变量名 a),就调用 addMapping 方法:

generator.addMapping({
  generated: { line: 1, column: 9 },  //  压缩代码位置
  original: { line: 1, column: 8 },   //  原始代码位置
  source: 'input.js',
  name: 'add'
});