Skip to main content

杂项

¥Keeping copyright notices or other comments

你可以传递 --comments 以在输出中保留某些注释。默认情况下,它将保留以 "!" 开头的注释和包含 "@preserve"、"@copyright"、"@license" 或 "@cc_on" 的 JSDoc 风格注释(IE 的条件编译)。你可以传递 --comments all 来保留所有注释,或者传递一个有效的 JavaScript 正则表达式来仅保留与此正则表达式匹配的注释。例如 --comments /^!/ 会保留像 /*! Copyright Notice */ 这样的注释。

¥You can pass --comments to retain certain comments in the output. By default it will keep comments starting with "!" and JSDoc-style comments that contain "@preserve", "@copyright", "@license" or "@cc_on" (conditional compilation for IE). You can pass --comments all to keep all the comments, or a valid JavaScript regexp to keep only comments that match this regexp. For example --comments /^!/ will keep comments like /*! Copyright Notice */.

但请注意,在某些情况下注释可能会丢失。例如:

¥Note, however, that there might be situations where comments are lost. For example:

function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}

即使它有 "@preserve",注释也会丢失,因为内部函数 g(注释所附加的 AST 节点)被压缩器丢弃,因为没有被引用。

¥Even though it has "@preserve", the comment will be lost because the inner function g (which is the AST node to which the comment is attached to) is discarded by the compressor as not referenced.

放置版权信息(或需要保留在输出中的其他信息)的最安全的注释是附加到顶层节点的注释。

¥The safest comments where to place copyright information (or other info that needs to be kept in the output) are comments attached to toplevel nodes.

unsafe compress 选项

¥The unsafe compress option

它支持一些转换,这些转换在某些人为的情况下可能会破坏代码逻辑,但对于大多数代码来说应该没问题。它假定标准内置 ECMAScript 函数和类没有被更改或替换。你可能想在自己的代码上尝试一下;它应该减少压缩的尺寸。启用此选项时进行的优化的一些示例:

¥It enables some transformations that might break code logic in certain contrived cases, but should be fine for most code. It assumes that standard built-in ECMAScript functions and classes have not been altered or replaced. You might want to try it on your own code; it should reduce the minified size. Some examples of the optimizations made when this option is enabled:

  • new Array(1, 2, 3)Array(1, 2, 3)[ 1, 2, 3 ]

    ¥new Array(1, 2, 3) or Array(1, 2, 3)[ 1, 2, 3 ]

  • Array.from([1, 2, 3])[1, 2, 3]

  • new Object(){}

  • String(exp)exp.toString()"" + exp

    ¥String(exp) or exp.toString()"" + exp

  • new Object/RegExp/Function/Error/Array (...) → 我们丢弃 new

    ¥new Object/RegExp/Function/Error/Array (...) → we discard the new

  • "foo bar".substr(4)"bar"

条件编译

¥Conditional compilation

你可以使用 --define (-d) 开关来声明 Terser 将假定为常量的全局变量(除非在作用域中定义)。例如,如果你传递 --define DEBUG=false,那么加上死代码删除,Terser 将从输出中丢弃以下内容:

¥You can use the --define (-d) switch in order to declare global variables that Terser will assume to be constants (unless defined in scope). For example if you pass --define DEBUG=false then, coupled with dead code removal Terser will discard the following from the output:

if (DEBUG) {
console.log("debug stuff");
}

你可以以 --define env.DEBUG=false 的形式指定嵌套常量。

¥You can specify nested constants in the form of --define env.DEBUG=false.

另一种方法是将全局变量声明为单独文件中的常量并将其包含到构建中。例如,你可以拥有一个包含以下内容的 build/defines.js 文件:

¥Another way of doing that is to declare your globals as constants in a separate file and include it into the build. For example you can have a build/defines.js file with the following:

var DEBUG = false;
var PRODUCTION = true;
// etc.

并像这样构建你的代码:

¥and build your code like this:

terser build/defines.js js/foo.js js/bar.js... -c

Terser 会注意到这些常量,并且由于它们无法更改,因此它将评估对它们的值本身的引用,并像往常一样删除无法访问的代码。如果你使用 const 声明,构建将包含它们。如果你的目标是不支持 const 的 < ES6 环境,则使用 varreduce_vars(默认启用)就足够了。

¥Terser will notice the constants and, since they cannot be altered, it will evaluate references to them to the value itself and drop unreachable code as usual. The build will contain the const declarations if you use them. If you are targeting < ES6 environments which does not support const, using var with reduce_vars (enabled by default) should suffice.

条件编译 API

¥Conditional compilation API

你还可以通过编程 API 使用条件编译。不同之处在于属性名称为 global_defs 并且是压缩器属性:

¥You can also use conditional compilation via the programmatic API. With the difference that the property name is global_defs and is a compressor property:

var result = await minify(fs.readFileSync("input.js", "utf8"), {
compress: {
dead_code: true,
global_defs: {
DEBUG: false
}
}
});

要将标识符替换为任意非常量表达式,需要在 global_defs 键前添加 "@" 前缀,以指示 Terser 将值解析为表达式:

¥To replace an identifier with an arbitrary non-constant expression it is necessary to prefix the global_defs key with "@" to instruct Terser to parse the value as an expression:

await minify("alert('hello');", {
compress: {
global_defs: {
"@alert": "console.log"
}
}
}).code;
// returns: 'console.log("hello");'

否则它将被替换为字符串字面量:

¥Otherwise it would be replaced as string literal:

await minify("alert('hello');", {
compress: {
global_defs: {
"alert": "console.log"
}
}
}).code;
// returns: '"console.log"("hello");'

注释

¥Annotations

Terser 中的注释是告诉它以不同方式对待某个函数调用的一种方式。可以使用以下注释:

¥Annotations in Terser are a way to tell it to treat a certain function call differently. The following annotations are available:

  • /*@__INLINE__*/ - 强制将函数内联到某处。

    ¥/*@__INLINE__*/ - forces a function to be inlined somewhere.

  • /*@__NOINLINE__*/ - 确保被调用的函数没有内联到调用站点中。

    ¥/*@__NOINLINE__*/ - Makes sure the called function is not inlined into the call site.

  • /*@__PURE__*/ - 将函数调用标记为纯函数。这意味着,它可以安全地掉落。

    ¥/*@__PURE__*/ - Marks a function call as pure. That means, it can safely be dropped.

  • /*@__KEY__*/ - 将字符串字面量标记为属性,以便在修改属性时也修改它。

    ¥/*@__KEY__*/ - Marks a string literal as a property to also mangle it when mangling properties.

  • /*@__MANGLE_PROP__*/ - 当启用属性混淆器时,选择对象属性(或类字段)进行混淆。

    ¥/*@__MANGLE_PROP__*/ - Opts-in an object property (or class field) for mangling, when the property mangler is enabled.

你可以在开头使用 @ 符号,也可以使用 #

¥You can use either a @ sign at the start, or a #.

以下是一些有关如何使用它们的示例:

¥Here are some examples on how to use them:

/*@__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 的转换器。

¥Terser has its own abstract syntax tree format; for practical reasons we can't easily change to using the SpiderMonkey AST internally. However, Terser now has a converter which can import a SpiderMonkey AST.

例如,Acorn 是一个超快的解析器,可以生成 SpiderMonkey AST。它有一个小型 CLI 实用程序,可以解析一个文件并将 AST 以 JSON 格式转储到标准输出上。要使用 Terser 来破坏和压缩它:

¥For example Acorn is a super-fast parser that produces a SpiderMonkey AST. It has a small CLI utility that parses one file and dumps the AST in JSON on the standard output. To use Terser to mangle and compress that:

acorn file.js | terser -p spidermonkey -m -c

-p spidermonkey 选项告诉 Terser 所有输入文件都不是 JavaScript,而是在 SpiderMonkey AST 中描述的 JSON 格式的 JS 代码。因此,在这种情况下,我们不使用自己的解析器,而只是将该 AST 转换为我们内部的 AST。

¥The -p spidermonkey option tells Terser that all input files are not JavaScript, but JS code described in SpiderMonkey AST in JSON. Therefore we don't use our own parser in this case, but just transform that AST into our internal AST.

spidermonkey 也可在 minify 中作为 parseformat 选项来接受和/或生成蜘蛛猴 AST。

¥spidermonkey is also available in minify as parse and format options to accept and/or produce a spidermonkey AST.

使用 Acorn 进行解析

¥Use Acorn for parsing

为了好玩,我添加了 -p acorn 选项,它将使用 Acorn 来完成所有解析。如果你通过此选项,Terser 将 require("acorn")

¥More for fun, I added the -p acorn option which will use Acorn to do all the parsing. If you pass this option, Terser will require("acorn").

Acorn 确实很快(例如,在某些 650K 代码上需要 250 毫秒,而不是 380 毫秒),但是转换 Acorn 生成的 SpiderMonkey 树又需要 150 毫秒,所以总的来说,它比仅使用 Terser 自己的解析器要多一点。

¥Acorn is really fast (e.g. 250ms instead of 380ms on some 650K code), but converting the SpiderMonkey tree that Acorn produces takes another 150ms so in total it's a bit more than just using Terser's own parser.

更简洁的快速压缩模式

¥Terser Fast Minify Mode

虽然不太为人所知,但对于大多数 JavaScript 来说,空格删除和符号混淆占了压缩代码大小的 95% - 没有详细的代码转换。只需禁用 compress 即可将 Terser 构建速度加快 3 到 4 倍。

¥It's not well known, but whitespace removal and symbol mangling accounts for 95% of the size reduction in minified code for most JavaScript - not elaborate code transforms. One can simply disable compress to speed up Terser builds by 3 to 4 times.

d3.jssize压缩包大小时间(秒)
original451,131108,733*
terser@3.7.5 mangle=false, compress=false316,60085,2450.82
terser@3.7.5 mangle=true, compress=false220,21672,7301.45
terser@3.7.5 mangle=true, compress=true212,04670,9545.87
巴比利@0.1.4210,71372,14012.64
babel-minify@0.4.3210,32172,24248.67
babel-minify@0.5.0-alpha.01eac1c3210,42172,23814.17

要从 CLI 启用快速压缩模式,请使用:

¥To enable fast minify mode from the CLI use:

terser file.js -m

要使用 API 启用快速压缩模式,请使用:

¥To enable fast minify mode with the API use:

await minify(code, { compress: false, mangle: true });

源映射和调试

¥Source maps and debugging

众所周知,各种简化、重新排列、内联和删除代码的 compress 转换会对使用源映射进行调试产生不利影响。这是预期的,因为代码已经过优化,并且映射通常根本不可能,因为某些代码不再存在。为了获得源映射调试的最高保真度,请禁用 compress 选项并仅使用 mangle

¥Various compress transforms that simplify, rearrange, inline and remove code are known to have an adverse effect on debugging with source maps. This is expected as code is optimized and mappings are often simply not possible as some code no longer exists. For highest fidelity in source map debugging disable the compress option and just use mangle.

调试时,请确保启用 "映射范围" 功能以将混淆的变量名称映射回其原始名称。如果没有这个,所有变量值都将为 undefined。详细信息请参见 https://github.com/terser/terser/issues/1367

¥When debugging, make sure you enable the "map scopes" feature to map mangled variable names back to their original names.\ Without this, all variable values will be undefined. See https://github.com/terser/terser/issues/1367 for more details.

image

编译器假设

¥Compiler assumptions

为了实现更好的优化,编译器做出了各种假设:

¥To allow for better optimizations, the compiler makes various assumptions:

  • .toString().valueOf() 没有副作用,并且对于内置对象,它们没有被覆盖。

    ¥.toString() and .valueOf() don't have side effects, and for built-in objects they have not been overridden.

  • undefinedNaNInfinity 尚未从外部重新定义。

    ¥undefined, NaN and Infinity have not been externally redefined.

  • arguments.calleearguments.callerFunction.prototype.caller 未使用。

    ¥arguments.callee, arguments.caller and Function.prototype.caller are not used.

  • 该代码并不期望 Function.prototype.toString()Error.prototype.stack 的内容有任何特殊内容。

    ¥The code doesn't expect the contents of Function.prototype.toString() or Error.prototype.stack to be anything in particular.

  • 获取和设置普通对象的属性不会导致其他副作用(使用 .watch()Proxy)。

    ¥Getting and setting properties on a plain object does not cause other side effects (using .watch() or Proxy).

  • 可以添加、删除和修改对象属性(Object.defineProperty()Object.defineProperties()Object.freeze()Object.preventExtensions()Object.seal() 不会阻止)。

    ¥Object properties can be added, removed and modified (not prevented with Object.defineProperty(), Object.defineProperties(), Object.freeze(), Object.preventExtensions() or Object.seal()).

  • document.all 不是 == null

    ¥document.all is not == null

  • 将属性分配给类不会产生副作用,也不会抛出异常。

    ¥Assigning properties to a class doesn't have side effects and does not throw.

使用 Terser 构建工具和适配器

¥Build Tools and Adaptors using Terser

https://www.npmjs.com/browse/depended/terser

在使用 yarn 的项目中将 uglify-es 替换为 terser

¥Replacing uglify-es with terser in a project using yarn

许多 JS 打包器和 uglify 封装器仍在使用有缺陷的 uglify-es 版本,并且尚未升级到 terser。如果你使用的是 yarn,你可以将以下别名添加到项目的 package.json 文件中:

¥A number of JS bundlers and uglify wrappers are still using buggy versions of uglify-es and have not yet upgraded to terser. If you are using yarn you can add the following alias to your project's package.json file:

  "resolutions": {
"uglify-es": "npm:terser"
}

在所有深度嵌套的依赖中使用 terser 而不是 uglify-es,而无需更改任何代码。

¥to use terser instead of uglify-es in all deeply nested dependencies without changing any code.

注意:要使此更改生效,你必须运行以下命令来删除现有的 yarn 锁定文件并重新安装所有软件包:

¥Note: for this change to take effect you must run the following commands to remove the existing yarn lock file and reinstall all packages:

$ rm -rf node_modules yarn.lock
$ yarn