各位好,今天咱们来聊聊 Terser 这个代码压缩界的“瘦身大师”,尤其是它那让人又爱又恨的 Mangling 和 Compression 选项。别怕,我会尽量用人话把这些概念掰开了揉碎了讲清楚,保证你们听完之后,也能像耍猴一样耍 Terser!
开场白:Terser 是个啥?
简单来说,Terser 是一个 JavaScript 的解析器、压缩器和 Mangler 工具集。它的主要作用就是把你的代码变得更小、更丑、更难懂,最终目的只有一个:让你的网站加载更快。
压缩代码,这个大家都能理解,就是去掉空格、注释之类的无用信息。但 Mangling 是个什么鬼? 别急,待我慢慢道来。
第一部分:Mangling – 代码界的“整容术”
Mangling,中文可以翻译成“变量名混淆”,就像给变量做了一次整容手术。它会把你的变量名、函数名改成一些没有意义的短字符串,比如 a
、b
、c
,甚至 _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();
可以看到,fullName
和 personAge
被混淆成了 a
和 b
。
第二部分: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 的精髓,成为代码压缩界的“瘦身大师”! 谢谢大家!