其他
保留版权声明或其他注释
您可以传递 --comments
来保留输出中的某些注释。默认情况下,它将保留以“!”开头的注释和包含“@preserve”、“@copyright”、“@license”或“@cc_on”(IE 的条件编译)的 JSDoc 样式注释。您可以传递 --comments all
来保留所有注释,或者传递一个有效的 JavaScript 正则表达式来仅保留与此正则表达式匹配的注释。例如,--comments /^!/
将保留像 /*! Copyright Notice */
这样的注释。
但是请注意,在某些情况下,注释可能会丢失。例如
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
即使它有“@preserve”,注释也会丢失,因为内部函数 g
(注释所附加到的 AST 节点)被压缩器丢弃,因为它没有被引用。
放置版权信息(或需要保留在输出中的其他信息)最安全的注释是附加到顶级节点的注释。
unsafe
compress
选项
它启用了一些*可能*会在某些人为情况下破坏代码逻辑的转换,但对于大多数代码来说应该是可以的。它假设标准的内置 ECMAScript 函数和类没有被更改或替换。您可能想在自己的代码上尝试一下;它应该可以减小压缩后的大小。以下是启用此选项时进行的一些优化示例
new Array(1, 2, 3)
或Array(1, 2, 3)
→[ 1, 2, 3 ]
Array.from([1, 2, 3])
→[1, 2, 3]
new Object()
→{}
String(exp)
或exp.toString()
→"" + exp
new Object/RegExp/Function/Error/Array (...)
→ 我们丢弃new
"foo bar".substr(4)
→"bar"
条件编译
您可以使用 --define
(-d
) 开关来声明全局变量,Terser 将假设这些变量是常量(除非在作用域中定义)。例如,如果您传递 --define DEBUG=false
,那么结合死代码删除,Terser 将从输出中丢弃以下内容
if (DEBUG) {
console.log("debug stuff");
}
您可以使用 --define env.DEBUG=false
的形式指定嵌套常量。
另一种方法是在单独的文件中将全局变量声明为常量,并将其包含在构建中。例如,您可以有一个包含以下内容的 build/defines.js
文件
var DEBUG = false;
var PRODUCTION = true;
// etc.
并像这样构建您的代码
terser build/defines.js js/foo.js js/bar.js... -c
Terser 会注意到这些常量,并且由于它们不能被更改,它会将对它们的引用评估为值本身,并像往常一样删除不可达的代码。如果您使用 const
声明,则构建将包含它们。如果您针对的是不支持 const
的 < ES6 环境,则使用带有 reduce_vars
的 var
(默认启用)就足够了。
条件编译 API
您还可以通过编程 API 使用条件编译。不同之处在于属性名称是 global_defs
并且是一个压缩器属性
var result = await minify(fs.readFileSync("input.js", "utf8"), {
compress: {
dead_code: true,
global_defs: {
DEBUG: false
}
}
});
要将标识符替换为任意的非常量表达式,需要在 global_defs
键前加上 "@"
,以指示 Terser 将该值解析为表达式
await minify("alert('hello');", {
compress: {
global_defs: {
"@alert": "console.log"
}
}
}).code;
// returns: 'console.log("hello");'
否则它将被替换为字符串字面量
await minify("alert('hello');", {
compress: {
global_defs: {
"alert": "console.log"
}
}
}).code;
// returns: '"console.log"("hello");'
注解
Terser 中的注解是一种告诉它以不同方式处理某个函数调用的方法。可以使用以下注解
/*@__INLINE__*/
- 强制在某处内联函数。/*@__NOINLINE__*/
- 确保被调用的函数没有被内联到调用站点。/*@__PURE__*/
- 将函数调用标记为纯函数。这意味着可以安全地删除它。/*@__KEY__*/
- 将字符串字面量标记为属性,以便在压缩属性时也压缩它。/*@__MANGLE_PROP__*/
- 在启用属性压缩器时,选择加入对象属性(或类字段)进行压缩。
您可以在开头使用 @
符号或 #
符号。
以下是一些关于如何使用它们的示例
/*@__INLINE__*/
function_always_inlined_here()
/*#__NOINLINE__*/
function_cant_be_inlined_into_here()
const x = /*#__PURE__*/i_am_dropped_if_x_is_not_used()
function lookup(object, key) { return object[key]; }
lookup({ i_will_be_mangled_too: "bar" }, /*@__KEY__*/ "i_will_be_mangled_too");
ESTree / SpiderMonkey AST
Terser 有自己的抽象语法树格式;由于实际原因,我们不能轻易地更改为在内部使用 SpiderMonkey AST。但是,Terser 现在有一个可以导入 SpiderMonkey AST 的转换器。
例如,Acorn 是一个超快的解析器,可以生成 SpiderMonkey AST。它有一个小的 CLI 实用程序,可以解析一个文件并将 AST 以 JSON 格式转储到标准输出中。要使用 Terser 来压缩和混淆它
acorn file.js | terser -p spidermonkey -m -c
-p spidermonkey
选项告诉 Terser 所有输入文件都不是 JavaScript,而是以 JSON 格式描述的 SpiderMonkey AST 中的 JS 代码。因此,在这种情况下,我们不使用我们自己的解析器,而是将 AST 转换为我们的内部 AST。
spidermonkey
在 minify
中也可用作 parse
和 format
选项,以接受和/或生成 spidermonkey AST。
使用 Acorn 进行解析
更有趣的是,我添加了 -p acorn
选项,它将使用 Acorn 来完成所有解析。如果您传递此选项,Terser 将 require("acorn")
。
Acorn 真的很快(例如,在一些 650K 的代码上,250 毫秒而不是 380 毫秒),但是转换 Acorn 生成的 SpiderMonkey 树需要另外 150 毫秒,所以总的来说,它比仅仅使用 Terser 自己的解析器要多一点。
Terser 快速压缩模式
这不是很为人所知,但对于大多数 JavaScript 来说,空格删除和符号压缩占了压缩代码大小减少的 95% - 而不是复杂的代码转换。可以简单地禁用 compress
来将 Terser 构建速度提高 3 到 4 倍。
d3.js | 大小 | gzip 大小 | 时间(秒) |
---|---|---|---|
原始 | 451,131 | 108,733 | - |
terser@3.7.5 mangle=false, compress=false | 316,600 | 85,245 | 0.82 |
terser@3.7.5 mangle=true, compress=false | 220,216 | 72,730 | 1.45 |
terser@3.7.5 mangle=true, compress=true | 212,046 | 70,954 | 5.87 |
babili@0.1.4 | 210,713 | 72,140 | 12.64 |
babel-minify@0.4.3 | 210,321 | 72,242 | 48.67 |
babel-minify@0.5.0-alpha.01eac1c3 | 210,421 | 72,238 | 14.17 |
要从 CLI 启用快速压缩模式,请使用
terser file.js -m
要使用 API 启用快速压缩模式,请使用
await minify(code, { compress: false, mangle: true });
源代码地图和调试
已知各种简化、重新排列、内联和删除代码的 compress
转换会对使用源代码地图进行调试产生不利影响。这是预料之中的,因为代码经过了优化,并且映射通常是不可能的,因为某些代码不再存在。为了在源代码地图调试中获得最高的保真度,请禁用 compress
选项,而只使用 mangle
。
调试时,请确保启用**“映射作用域”**功能,以将压缩后的变量名映射回其原始名称。
如果没有这个,所有变量的值都将是 undefined
。有关更多详细信息,请参阅 https://github.com/terser/terser/issues/1367。
编译器假设
为了更好地优化,编译器会做出各种假设
.toString()
和.valueOf()
没有副作用,并且对于内置对象,它们没有被覆盖。undefined
、NaN
和Infinity
没有被外部重新定义。- 没有使用
arguments.callee
、arguments.caller
和Function.prototype.caller
。 - 代码不期望
Function.prototype.toString()
或Error.prototype.stack
的内容是特定的。 - 在普通对象上获取和设置属性不会导致其他副作用(使用
.watch()
或Proxy
)。 - 可以添加、删除和修改对象属性(不会被
Object.defineProperty()
、Object.defineProperties()
、Object.freeze()
、Object.preventExtensions()
或Object.seal()
阻止)。 document.all
不等于null
- 为类分配属性没有副作用,也不会抛出异常。
使用 Terser 的构建工具和适配器
https://npmjs.net.cn/browse/depended/terser
在使用 yarn
的项目中将 uglify-es
替换为 terser
许多 JS 打包器和 uglify 包装器仍在使用错误版本的 uglify-es
,并且尚未升级到 terser
。如果您使用的是 yarn
,则可以将以下别名添加到项目的 package.json
文件中
"resolutions": {
"uglify-es": "npm:terser"
}
以便在所有深度嵌套的依赖项中使用 terser
而不是 uglify-es
,而无需更改任何代码。
注意:要使此更改生效,您必须运行以下命令以删除现有的 yarn
锁定文件并重新安装所有软件包
$ rm -rf node_modules yarn.lock
$ yarn