JS `Terser` `Mangling` 与 `Compression` 选项的精细控制

各位好,今天咱们来聊聊 Terser 这个代码压缩界的“瘦身大师”,尤其是它那让人又爱又恨的 Mangling 和 Compression 选项。别怕,我会尽量用人话把这些概念掰开了揉碎了讲清楚,保证你们听完之后,也能像耍猴一样耍 Terser!

开场白:Terser 是个啥?

简单来说,Terser 是一个 JavaScript 的解析器、压缩器和 Mangler 工具集。它的主要作用就是把你的代码变得更小、更丑、更难懂,最终目的只有一个:让你的网站加载更快。

压缩代码,这个大家都能理解,就是去掉空格、注释之类的无用信息。但 Mangling 是个什么鬼? 别急,待我慢慢道来。

第一部分:Mangling – 代码界的“整容术”

Mangling,中文可以翻译成“变量名混淆”,就像给变量做了一次整容手术。它会把你的变量名、函数名改成一些没有意义的短字符串,比如 abc,甚至 _0x1234 这种鬼东西。

为什么要 Mangling?

  • 减小代码体积: 短变量名肯定比长变量名占用的空间小。
  • 增加代码阅读难度: 混淆后的代码更难被逆向工程,增加一定的安全性(虽然防不住专业的黑客,但至少能吓退一些菜鸟)。

Mangling 的选项:

Terser 提供了丰富的 Mangling 选项,让你能够精细控制混淆的过程。

选项 描述 示例
mangle 总开关,决定是否启用 Mangling。默认值为 true mangle: true(启用) 或 mangle: false(禁用)
mangle.properties 用于混淆属性名。这是一个非常强大的功能,但也需要谨慎使用,因为一不小心就会搞砸你的代码。 详见下面的 mangle.properties 章节
mangle.reserved 指定哪些变量名不能被混淆。通常用于保留一些全局变量或重要的变量,防止混淆导致代码出错。 mangle: { reserved: ['$', '_', 'myGlobalVar'] }
mangle.eval 启用或禁用对 eval() 表达式中使用的变量的混淆。默认值为 false mangle: { eval: true }(启用) 或 mangle: { eval: false }(禁用)
mangle.keep_fnames 保留函数名。如果你需要依赖函数名来进行调试或依赖某些第三方库,可以启用这个选项。默认值为 false mangle: { keep_fnames: true }(保留函数名) 或 mangle: { keep_fnames: false }(混淆函数名)
mangle.toplevel 是否混淆顶级作用域中的变量名。默认值为 false mangle: { toplevel: true }(混淆顶级作用域变量) 或 mangle: { toplevel: false }(不混淆顶级作用域变量)

mangle.properties 详解

mangle.properties 是一个进阶选项,它可以混淆对象属性名。这在减小代码体积方面效果显著,但使用起来也更复杂。

  • 基本用法:

    mangle: {
      properties: true
    }

    这种方式会混淆所有非内置的属性名。

  • 更精细的控制:

    mangle.properties 还可以接受一个对象,用于更精细地控制混淆过程。

    mangle: {
      properties: {
        regex: /^_/, // 只混淆以下划线开头的属性名
        reserved: ['prop1', 'prop2'], // 排除 prop1 和 prop2 属性名
        debug: true // 在混淆后的属性名前添加原始属性名作为后缀,方便调试
      }
    }
    • regex: 一个正则表达式,用于匹配需要混淆的属性名。
    • reserved: 一个数组,包含不需要混淆的属性名。
    • debug: 一个布尔值,如果设置为 true,Terser 会在混淆后的属性名前添加原始属性名作为后缀,方便调试。例如,_myProp 会被混淆成 a_myProp
  • mangle.properties.builtins: 默认情况下,Terser 不会混淆内置属性(例如 length, prototype)。将其设置为 true 可以强制混淆这些属性。 极其不推荐,通常会导致代码崩溃。

    mangle: {
        properties: {
            builtins: true // 极其危险!
        }
    }

mangle.properties 的注意事项:

  • 破坏性: mangle.properties 是一个具有破坏性的选项,使用不当会导致代码出错。在使用之前,一定要充分测试。
  • 作用域: mangle.properties 会跨文件混淆属性名。也就是说,如果你的项目包含多个文件,并且这些文件共享相同的属性名,那么这些属性名会被混淆成相同的字符串。
  • 第三方库: 如果你的代码依赖第三方库,混淆属性名可能会破坏第三方库的正常运行。

代码示例:Mangling

// 原始代码
function Person(name, age) {
  this.fullName = name;
  this.personAge = age;
}

Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.fullName + " and I am " + this.personAge + " years old.");
};

var john = new Person("John Doe", 30);
john.greet();

使用以下 Terser 配置:

{
  mangle: {
    properties: {
      regex: /^[a-z]/
    }
  }
}

混淆后的代码:

function Person(n, a) {
  this.a = n;
  this.b = a;
}
Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.a + " and I am " + this.b + " years old.");
};
var john = new Person("John Doe", 30);
john.greet();

可以看到,fullNamepersonAge 被混淆成了 ab

第二部分:Compression – 代码界的“榨汁机”

Compression,中文可以翻译成“压缩”,就像把代码放到榨汁机里榨干水分一样。它会移除代码中的空格、注释、死代码等无用信息,从而减小代码体积。

为什么要 Compression?

  • 减小代码体积: 这是最直接的目的。
  • 提高代码加载速度: 代码体积越小,加载速度越快。

Compression 的选项:

Terser 提供了大量的 Compression 选项,让你能够精细控制压缩的过程。

选项 描述 示例
compress 总开关,决定是否启用 Compression。默认值为 true compress: true(启用) 或 compress: false(禁用)
compress.dead_code 移除死代码。默认值为 true compress: { dead_code: true }(启用) 或 compress: { dead_code: false }(禁用)
compress.drop_console 移除 console.* 调用。默认值为 false。在生产环境中,通常需要移除 console.log 等调试语句。 compress: { drop_console: true }(移除) 或 compress: { drop_console: false }(保留)
compress.drop_debugger 移除 debugger 语句。默认值为 true compress: { drop_debugger: true }(移除) 或 compress: { drop_debugger: false }(保留)
compress.evaluate 尝试评估常量表达式。默认值为 true。例如,1 + 2 会被替换成 3 compress: { evaluate: true }(启用) 或 compress: { evaluate: false }(禁用)
compress.expression 如果设置为 true,则会解析并压缩单个表达式,而不是整个程序。这通常用于压缩模板字符串中的 JavaScript 代码。默认值为 false compress: { expression: true }(启用) 或 compress: { expression: false }(禁用)
compress.global_defs 定义全局变量的值。这可以用于在编译时替换一些常量。默认值为 {} compress: { global_defs: { DEBUG: false } }(定义 DEBUG 变量为 false)
compress.passes 指定运行压缩过程的次数。默认值为 1。增加 passes 的值可以提高压缩率,但也可能增加编译时间。 compress: { passes: 2 }(运行 2 次压缩)
compress.pure_funcs 指定哪些函数是纯函数。纯函数是指没有副作用的函数,也就是说,函数的返回值只取决于输入参数,并且不会修改任何外部状态。Terser 可以利用纯函数的特性进行更积极的优化。默认值为 null compress: { pure_funcs: ['Math.floor'] }(指定 Math.floor 是纯函数)
compress.pure_getters 如果设置为 true,则假定所有 getter 都是纯的。默认值为 false compress: { pure_getters: true }(假定 getter 是纯的) 或 compress: { pure_getters: false }(不假定 getter 是纯的)
compress.unsafe 启用一些不安全的优化。默认值为 false。这些优化可能会破坏一些代码的正常运行,所以需要谨慎使用。 compress: { unsafe: true }(启用) 或 compress: { unsafe: false }(禁用)
compress.unsafe_comps 允许比较具有副作用的表达式。默认值为 false compress: { unsafe_comps: true }(允许) 或 compress: { unsafe_comps: false }(不允许)
compress.unsafe_Function Function(args, body) 替换为相应的箭头函数 (...args) => body。默认值为 false compress: { unsafe_Function: true }(替换) 或 compress: { unsafe_Function: false }(不替换)
compress.unsafe_math 对数学运算进行不安全的转换。默认值为 false compress: { unsafe_math: true }(启用) 或 compress: { unsafe_math: false }(禁用)
compress.unsafe_proto 对原型链进行不安全的访问。默认值为 false compress: { unsafe_proto: true }(启用) 或 compress: { unsafe_proto: false }(禁用)
compress.toplevel 如果设置为 true,则会压缩顶级作用域中的代码。默认值为 false compress: { toplevel: true }(压缩) 或 compress: { toplevel: false }(不压缩)
compress.keep_fargs 防止丢弃未使用的函数参数。默认值为 true compress: { keep_fargs: false } (丢弃) 或 compress: { keep_fargs: true } (保留)

代码示例:Compression

// 原始代码
function calculateArea(width, height) {
  // This function calculates the area of a rectangle.
  var area = width * height;
  console.log("The area is: " + area);
  return area;
}

calculateArea(10, 5);

使用以下 Terser 配置:

{
  compress: {
    dead_code: true,
    drop_console: true,
    evaluate: true
  }
}

压缩后的代码:

function calculateArea(width, height) {
  return width * height;
}
calculateArea(10, 5);

可以看到,注释被移除,console.log 调用被移除,area 变量被内联到 return 语句中。

第三部分:Mangling 和 Compression 的配合使用

Mangling 和 Compression 通常一起使用,以达到最佳的压缩效果。

{
  mangle: true,
  compress: {
    dead_code: true,
    drop_console: true,
    evaluate: true
  }
}

这种配置会同时启用 Mangling 和 Compression,最大程度地减小代码体积。

第四部分:Terser 的配置方式

Terser 可以通过多种方式进行配置:

  • 命令行:

    terser input.js -o output.min.js --mangle --compress
  • 配置文件:

    创建一个 terser.config.js 文件:

    module.exports = {
      mangle: true,
      compress: {
        dead_code: true,
        drop_console: true,
        evaluate: true
      }
    };

    然后在命令行中使用 --config-file 选项:

    terser input.js -o output.min.js --config-file terser.config.js
  • 构建工具:

    大多数构建工具(例如 Webpack、Parcel、Rollup)都提供了 Terser 的插件,方便你在构建过程中进行代码压缩。

第五部分:调试压缩后的代码

压缩后的代码很难阅读,这给调试带来了很大的挑战。以下是一些调试压缩后代码的技巧:

  • Source Maps:

    Source Maps 是一种将压缩后的代码映射回原始代码的技术。它可以让你在浏览器中调试压缩后的代码时,看到的是原始代码。

    Terser 提供了生成 Source Maps 的选项:

    terser input.js -o output.min.js --source-map
  • mangle.properties.debug

    在混淆属性名时,可以使用 mangle.properties.debug 选项在混淆后的属性名前添加原始属性名作为后缀,方便调试。

  • 逐步禁用压缩选项:

    如果你的代码在压缩后出现问题,可以逐步禁用压缩选项,找到导致问题的选项。

总结:

Terser 是一个强大的 JavaScript 代码压缩工具,它提供了丰富的 Mangling 和 Compression 选项,让你能够精细控制压缩的过程。但是,Mangling 和 Compression 也是具有破坏性的操作,使用不当会导致代码出错。因此,在使用之前,一定要充分测试,并了解每个选项的作用。

最后的忠告:

记住,代码压缩的目的是为了提高网站的性能,而不是为了让你的代码更难懂。在追求最佳压缩效果的同时,也要兼顾代码的可维护性和可调试性。

好了,今天的讲座就到这里。希望你们能够掌握 Terser 的精髓,成为代码压缩界的“瘦身大师”! 谢谢大家!

发表回复

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