使用

  1. uglifyjs [input files] [options]

UglifyJS2可以输入多文件。建议你先写输入文件,再传选项。UglifyJS会根据压缩选项,把文件放在队列中依次解释。所有文件都会在同一个全局域中,假如一个文件中的变量、方法被另一文件引用,UglifyJS会合理地匹配。

假如你不要输入文件,而是要输入字符串(STDIN),那就把文件名换成一个横线(-)

如果你想要把选项写在文件名的前面,那要在二者之前加上双横线,防止文件名被当成了选项:

  1. uglifyjs --compress --mangle -- input.js

以下是可用的选项:

  1. --source-map 指定输出的文件产生一份sourcemap
  2. --source-map-root 此路径中的源码编译后会产生sourcemap
  3. --source-map-url 放在//#sourceMappingURL的sourcemap路径. 默认是
  4. --source-map传入的值.
  5. --source-map-include-sources 如果你要在sourcemap中加上源文件的内容作为sourcesContent属性,
  6. 就传这个参数吧。
  7. --source-map-inline sourcemapbase64格式附在输出文件结尾
  8. --in-source-map 输入sourcemap。假如的你要编译的JS是另外的源码编译出来的。
  9. 假如该sourcemap包含在js内,请指定"inline"
  10. --screw-ie8 是否要支持IE6/7/8UglifyJS默认不兼容IE
  11. --support-ie8 是否要支持IE6/7/8,等同于在`compress`, `mangle`
  12. `output`选项中都设置`screw_ie8: false`
  13. --expr 编译一个表达式,而不是编译一段代码(编译JSON时用)
  14. -p, --prefix 忽略sourcemap中源码的前缀。例如`-p 3`会干掉文件名前面3层目录
  15. 以及保证路径是相对路径。你也可以指定`-p relative`,让UglifyJS
  16. 自己计算输出文件、sourcemap与源码之间的相对路径。
  17. -o, --output 输出文件,默认标准输出(STDOUT)
  18. -b, --beautify 美化输出/指定输出 选项
  19. -m, --mangle Mangle的名字,或传入一个mangler选项.
  20. -r, --reserved mangle的例外,不包含在mangling的名字
  21. -c, --compress 是否启用压缩功能(true/fasle),或者传一个压缩选项对象, 例如
  22. `-c 'if_return=false,pure_funcs=["Math.pow","console.log"]'`
  23. `-c`不带参数的话就是用默认的压缩设置。
  24. -d, --define 全局定义
  25. -e, --enclose 所有代码嵌入到一个大方法中,传入参数为配置项
  26. --comments 保留版权注释。默认保留Google Closure那样的,保留JSDoc-style
  27. 包含"@license" "@preserve"字样的注释。你也可以传下面的参数:
  28. - "all" 保留所有注释
  29. - 正则(如`/foo/``/^!/`)保留匹配到的。要注意,如果启用了压
  30. 缩,因为会移除不可达代码以及压缩连续声明,因此不是*所有*注释都能
  31. 保留下来。
  32. --preamble 在输出文件开头插入的前言。你可以插入一段注释,例如版权信息。
  33. 这些不会被编译,但sourcemap会改成当前的样子。
  34. --stats STDERR中显示操作运行时间。
  35. --acorn Acorn解析。
  36. --spidermonkey 假如输入文件是 SpiderMonkey AST 格式(像JSON).
  37. --self UglifyJS2本身也构建成一个依赖包
  38. (等同于`--wrap=UglifyJS --export-all`)
  39. --wrap 所有代码嵌入到一个大函数中,让"exports""global"变量有效,
  40. 你需要传入一个参数指定模块被浏览器引入时的名字。
  41. --export-all 只当`--wrap`时有效,告诉UglifyJS自动把代码暴露到全局。
  42. --lint 显示一些可视警告
  43. -v, --verbose Verbose
  44. -V, --version 打印版本号.
  45. --noerr 不要为-c,-b -m选项中出现未知选项而抛出错误。
  46. --bare-returns 允许返回函数的外部。当最小化CommonJs模块和Userscripts时,
  47. 可能匿名函数会被.user.js引擎调用立即执行(IIFE
  48. --keep-fnames 不要混淆、干掉的函数的名字。当代码依赖Function.prototype.name时有用。
  49. --reserved-file 要保留的文件的名字
  50. --reserve-domprops 保留(绝大部分?)DOM的属性,当--mangle-props
  51. --mangle-props 混淆属性,默认是`0`.设置为`true``1`则会混淆所有属性名。
  52. 设为`unquoted` `2`则只混淆不在引号内的属性。`2`时也会让
  53. `keep_quoted_props` 美化选项生效,保留括号内的属性;让压缩选项
  54. `properties`失效,阻止覆写带点号(.)的属性。你可以通过在命令
  55. 中明确设置来覆写它们。
  56. --mangle-regex 混淆正则,只混淆匹配到的属性名。
  57. --name-cache 用来保存混淆map的文件
  58. --pure-funcs 假如返回值没被调用则可以安全移除的函数。
  59. 例如`--pure-funcs Math.floor console.info`(需要设置 `--compress`)

指定--output (-o)来明确输出文件,否则将在终端输出(STDOUT)

sourcemap选项

Source map options

UglifyJS2可以生成一份sourcemap文件,这对调试你压缩后的JS代码非常有用。传--source-map output.js.map(完整路径)来获取sorcemap文件。

另外,你可能要设置--source-map-root传入源码所在的根目录。为了防止出现整个路径,你可以用--prefix (-p)指定干掉几层soucemap中路径的前缀。

例如:

  1. uglifyjs /home/doe/work/foo/src/js/file1.js \
  2. /home/doe/work/foo/src/js/file2.js \
  3. -o foo.min.js \
  4. --source-map foo.min.js.map \
  5. --source-map-root http://foo.com/src \
  6. -p 5 -c -m

上述配置会压缩和混淆file1.jsfile2.js,输出文件foo.min.js 和sourcemapfoo.min.js.map,sourcemap会建立http://foo.com/src/js/file1.js
http://foo.com/src/js/file2.js的映射。(实际上,sourcemap根目录是http://foo.com/src,所以相当于源文件路径是js/file1.jsjs/file2.js

关联sourcemap

假如你的JS代码是用其他编译器(例如coffeescript)生成的,那么映射到JS代码就没什么用了,你肯定希望映射到CoffeeScript源码。UglifyJS有一个选项可以输入sourcemap,假如你有一个从CoffeeScript → 编译后JS的map的话,UglifyJS可以生成一个从CoffeeScript->压缩后JS的map映射到源码位置。

你可以传入 --in-source-map /path/to/input/source.map来尝试此特性,如果sourcemap包含在js内,则写--in-source-map inline 。通常输入的sourcemap会指向源代码生成的JS,所以你可以忽略不写输入文件。

混淆选项

Mangler options

你需要传入--mangle (-m)来使启用混淆功能。支持用逗号隔开选项:

  • toplevel — 混淆在最高作用域中声明的变量名(默认disabled)

  • eval - 混淆在evalwith作用域出现的变量名(默认disabled)

当启用混淆功能时,如果你希望保留一些名字不被混淆,你可以用--reserved (-r) 声明一些名字,用逗号隔开。例如:

  1. uglifyjs ... -m -r '$,require,exports'

防止require, exports$被混淆改变。

混淆属性名 (--mangle-props)

警告:这能会搞崩你的代码。混淆属性名跟混淆变量名不一样,是相互独立的。传入--mangle-props会混淆对象所有可见的属性名。例如:

  1. var x = {
  2. foo: 1
  3. };
  4. x.bar = 2;
  5. x["baz"] = 3;
  6. x[condition ? "moo" : "boo"] = 4;
  7. console.log(x.something());

上面代码中,foo, bar, baz, mooboo会被替换成单字符名字,something()则不变。

为了合理地使用,我们应该避免混淆一些JS标准的名字。比如,如果你代码中有x.length = 10,那length就将被混淆,不管这是在对象中还是访问数组的长度,它都被干掉。为了避免这种情况,你可以用 --reserved-file来输入一个文件,里面包含不参与混淆的名字,变量名或属性名都行。就像下面这样:

  1. {
  2. "vars": [ "define", "require", ... ],
  3. "props": [ "length", "prototype", ... ]
  4. }

--reserved-file 可以是文件名数组(用逗号隔开,你也可以传多个--reserved-file),在上面例子中的名字将被排除在混淆中。
tools/domprops.json 里有一个默认的排除名单,包括绝大部分标准JS和多种浏览器中的DOM属性名。传入--reserve-domprops 可以读取此名单生效。

你也可以用正则表达式来定义一些应该被混淆的属性名。例如--mangle-regex="/^_/",会只混淆以下划线开始的属性名。

当你压缩多个文件时,为了保证让它们最终能同时工作,我们要让他们中同样的属性名混淆成相同的结果。传入--name-cache filename.json,UglifyJS会维护一个共同的映射供他们复用。这个json一开始应该是空的,例如:

  1. rm -f /tmp/cache.json # start fresh
  2. uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
  3. uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js

现在,part1.jspart2.js会知晓对方混淆的属性名。

假如你把所有文件压缩成同一个文件,那就不需要启用名字缓存了。

混淆括号中的名字(--mangle-props=unquoted--mangle-props=2)

使用括号属性名 (o["foo"])以保留属性名(foo)。这会让整个脚本中其余此属性的引用(o.foo)也不被混淆。例如:

  1. $ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props=2 -mc
  2. var o={"foo":1,a:3};o.foo+=o.a,console.log(o.foo);

调试属性名混淆

为了混淆属性时不至于完全糊涂,你可以传入--mangle-props-debug来调试。例如o.foo会被混淆成o._$foo$_。这让源码大量、属性被混淆时也可以debug,可以看清混淆会把哪些属性搞乱。

你可以用--mangle-props-debug=XYZ来传入自定义后缀。让o.foo 混淆成 o._$foo$XYZ_, 你可以在每次编译是都改变一下,来辨清属性名怎么被混淆的。一个小技巧,你可以每次编译时传随机数来模仿混淆操作(例如你更新了脚本,有了新的属性名),这有助于识别混淆时的出错。

压缩器选项

Compressor options

你要传入 --compress (-c)来启用压缩功能。你可以用逗号隔开选项。选项的形式为foo=bar,或者就foo(后者等同于你要设为true,相当于foo=true的缩写)。

  • sequences(默认true) — 连续声明变量,用逗号隔开来。可以设置为正整数来指定连续声明的最大长度。如果设为true 表示默认200个,设为false0则禁用。 sequences至少要是2,1的话等同于true(即200)。默认的sequences设置有极小几率会导致压缩很慢,所以推荐设置成20或以下。

  • properties — 用.来重写属性引用,例如foo["bar"] → foo.bar

  • dead_code — 移除没被引用的代码

  • drop_debugger — 移除 debugger;

  • unsafe (默认 false) — 使用 “unsafe”转换 (下面详述)

  • unsafe_comps (默认 false) — 保留<<=不被换成 >>=。假如某些运算对象是用getvalueOfobject得出的时候,转换可能会不安全,可能会引起运算对象的改变。此选项只有当 comparisonsunsafe_comps 都设为true时才会启用。

  • unsafe_math (默认 false) — 优化数字表达式,例如2 * x * 3 变成 6 * x, 可能会导致不精确的浮点数结果。

  • unsafe_proto (默认 false) — 把Array.prototype.slice.call(a) 优化成 [].slice.call(a)

  • conditionals — 优化if等判断以及条件选择

  • comparisons — 把结果必然的运算优化成二元运算,例如!(a <= b) → a > b (只有设置了 unsafe_comps时才生效);尽量转成否运算。例如 a = !b && !c && !d && !e → a=!(b||c||d||e)

  • evaluate — 尝试计算常量表达式

  • booleans — 优化布尔运算,例如 !!a? b : c → a ? b : c

  • loops — 当dowhilefor循环的判断条件可以确定是,对其进行优化。

  • unused — 干掉没有被引用的函数和变量。(除非设置"keep_assign",否则变量的简单直接赋值也不算被引用。)

  • toplevel — 干掉顶层作用域中没有被引用的函数 ("funcs")和/或变量("vars") (默认是false , true 的话即函数变量都干掉)

  • top_retain — 当设了unused时,保留顶层作用域中的某些函数变量。(可以写成数组,用逗号隔开,也可以用正则或函数. 参考toplevel)

  • hoist_funs — 提升函数声明

  • hoist_vars (默认 false) — 提升 var 声明 (默认是false,因为那会加大文件的size)

  • if_return — 优化 if/return 和 if/continue

  • join_vars — 合并连续 var 声明

  • cascade — 弱弱地优化一下连续声明, 将 x, x 转成 xx = something(), x 转成 x = something()

  • collapse_vars — 当 varconst 单独使用时尽量合并

  • reduce_vars — 优化某些变量实际上是按常量值来赋值、使用的情况。

  • warnings — 当删除没有用处的代码时,显示警告

  • negate_iife — 当立即执行函数(IIFE)的返回值没用时,取消之。避免代码生成器会插入括号。

  • pure_getters — 默认是 false. 如果你传入true,UglifyJS会假设对象属性的引用(例如foo.barfoo["bar"])没有函数副作用。

  • pure_funcs — 默认 null. 你可以传入一个名字的数组,UglifyJS会假设这些函数没有函数副作用。警告:假如名字在作用域中重新定义,不会再次检测。例如var q = Math.floor(a/b),假如变量q没有被引用,UglifyJS会干掉它,但 Math.floor(a/b)会被保留,没有人知道它是干嘛的。你可以设置pure_funcs: [ 'Math.floor' ] ,这样该函数会被认为没有函数副作用,这样整个声明会被废弃。在目前的执行情况下,会增加开销(压缩会变慢)。

  • drop_console — 默认 false. 传true的话会干掉console.*函数。如果你要干掉特定的函数比如console.info ,又想删掉后保留其参数中的副作用,那用pure_funcs来处理吧。

  • expression — 默认 false。传true来保留终端语句中没有”return”的完成值。例如在bookmarklets。

  • keep_fargs — 默认true。阻止压缩器干掉那些没有用到的函数参数。你需要它来保护某些依赖Function.length的函数。

  • keep_fnames — 默认 false。传 true来防止压缩器干掉函数名。对那些依赖Function.prototype.name的函数很有用。延展阅读:keep_fnames 混淆选项.

  • passes — 默认 1。运行压缩的次数。在某些情况下,用一个大于1的数字参数可以进一步压缩代码大小。注意:数字越大压缩耗时越长。

  • keep_infinity — 默认 false。传true以防止压缩时把1/0转成Infinity,那可能会在chrome上有性能问题。

unsafe选项

在某些刻意营造的案例中,启用某些转换有可能会打断代码的逻辑,但绝大部分情况下是安全的。你可能会想尝试一下,因为这毕竟会减少文件体积。以下是某些例子:

  • new Array(1, 2, 3)Array(1, 2, 3)[ 1, 2, 3 ]
  • new Object(){}
  • String(exp)exp.toString()"" + exp
  • new Object/RegExp/Function/Error/Array (...) → 我们干掉用new
  • typeof foo == "undefined"foo === void 0
  • void 0undefined (假如作用域中有一个变量名叫”undefined”;我们这么做是因为变量名会被混淆成单字符)

编译条件语句

Uglify会假设全局变量都是常量(不管是否在局部域中定义了),你可以用--define (-d)来实现定义全局变量。例如你传--define DEBUG=false,UglifyJS会在输出中干掉下面代码:

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

你可以像--define env.DEBUG=false这样写嵌套的常量。

在干掉那些永否的条件语句以及不可达代码时,UglifyJS会给出警告。现在没有选项可以禁用此特性,但你可以设置 warnings=false 来禁掉所有警告。

另一个定义全局常量的方法是,在一个独立的文档中定义,再引入到构建中。例如你有一个这样的build/defines.js

  1. const DEBUG = false;
  2. const PRODUCTION = true;
  3. // 等等

构建使用这样写:

  1. uglifyjs build/defines.js js/foo.js js/bar.js... -c

UglifyJS会注意到这些常量。因为它们无法改变,所以它们会被认为是没被引用而被照样干掉。如果你用const声明,构建后还会被保留。如果你的运行环境低于ES6、不支持const,请用var声明加上reduce_vars设置(默认启用)来实现。

编译条件语句API

你也可以通过程序API来设置编译配置。其中有差别的是一个压缩器属性global_defs

  1. uglifyJS.minify([ "input.js"], {
  2. compress: {
  3. dead_code: true,
  4. global_defs: {
  5. DEBUG: false
  6. }
  7. }
  8. });

美化器选项(Beautifier options)

代码生成器默认会输出尽量简短的代码。假如你想美化一下输出代码,请设置--beautify (-b)。你可以传入更多可选的选项参数来控制代码生成:

  • beautify (默认 true) — 是否美化输出代码。传-b的话就是设成true。假如你想生成最小化的代码同时又要用其他设置来美化代码,你可以设-b beautify=false
  • indent-level (默认 4) 缩进格数
  • indent-start (默认 0) — 每行前面加几个空格
  • quote-keys (默认 false) — 传true的话会在对象所有的键加上括号
  • space-colon (默认 true) — 在冒号后面加空格
  • ascii-only (默认 false) — 避免Unicode字符在字符串/正则中出现(非ascii字符会变不合法)。
  • inline-script (默认 false) — 避免字符串中出现</script中的斜杠
  • width (默认 80) — 仅在美化时生效,设定一个行宽让美化器尽量实现。这会影响行中文字的数量(不包括缩进)。当前本功能实现得不是非常好,但依然让美化后的代码可读性大大增强。
  • max-line-len (默认 32000) — 最大行宽(压缩后的代码)
  • bracketize (默认 false) — 永远在if, for,do, while, with后面加上大括号,即使循环体只有一句。
  • semicolons (默认 true) — 用分号分开多个声明。如果你传false,则总会另起一行,增强输出文件的可读性。(gzip前体积更小,gzip后稍大一点点)
  • preamble (默认 null) — 如果要传的话,必须是字符串。它会被加在输出文档的前面。sourcemap会随之调整。例如可以用来插入版权信息。
  • quote_style (默认 0) — 影响字符串的括号格式(也会影响属性名和指令)。
  • 0 — 倾向使用双引号,字符串里还有引号的话就是单引号。
  • 1 — 永远单引号
  • 2 — 永远双引号
  • 3 — 永远是本来的引号
  • keep_quoted_props (默认 false) — 如果启用,会保留属性名的引号。

保留版权告示和其他注释

你可以传入--comments让输出文件中保留某些注释。默认时会保留JSDoc-style的注释(包含”@preserve”,”@license” 或 “@cc_on”(为IE所编译))。你可以传入--comments all来保留全部注释,或者传一个合法的正则来保留那些匹配到的注释。例如--comments '/foo|bar/'会保留那些包含”foo” 或 “bar”的注释。

注意,无论如何,总会有些注释在某些情况下会丢失。例如:

  1. function f() {
  2. /** @preserve Foo Bar */
  3. function g() {
  4. // this function is never called
  5. }
  6. return something();
  7. }

即使里面带有”@preserve”,注释依然会被丢弃。因为内部的函数g(注释所依附的抽象语法树节点)没有被引用、会被压缩器干掉。

书写版权信息(或其他需要在输出文件中保留的信息)的最安全位置是全局节点。

对SpiderMonkey的支持

UglifyJS2有自己的抽象语法树格式;为了某些现实的原因
我们无法在内部轻易地改成使用SpiderMonkey抽象语法树(AST)。但UglifyJS现在有了一个可以输入SpiderMonkeyAST的转换器。
例如[Acorn][acorn] ,这是一个超级快的生成SpiderMonkey AST的解释器。它带有一个实用的迷你CLI,能解释一个文件、把AST转存为JSON并标准输出。可以这样用UglifyJS来压缩混淆:

  1. acorn file.js | uglifyjs --spidermonkey -m -c

--spidermonkey选项能让UglifyJS知道输入文件并非JavaScript,而是SpiderMonkey AST生成的JSON代码。这事我们不用自己的解释器,只把AST转成我们内部AST。

使用 Acorn 来解释代码

更有趣的是,我们加了 --acorn选项来使用Acorn解释所有代码。如果你传入这个选项,UglifyJS会require("acorn")

Acorn确实非常快(650k代码原来要380ms,现在只需250ms),但转换Acorn产生的SpiderMonkey树会额外花费150ms。所以总共比UglifyJS自己的解释器还要多花一点时间。

使用 UglifyJS 转换 SpiderMonkey AST

现在你可以像使用其他中间工具一样使用UglifyJS将JS抽象语法树转换为SpiderMonkey格式。

例如:

  1. function uglify(ast, options, mangle) {
  2. // 把SpiderMonkey AST 转成中间格式
  3. var uAST = UglifyJS.AST_Node.from_mozilla_ast(ast);
  4. // 压缩
  5. uAST.figure_out_scope();
  6. uAST = UglifyJS.Compressor(options).compress(uAST);
  7. // 混淆 (可选)
  8. if (mangle) {
  9. uAST.figure_out_scope();
  10. uAST.compute_char_frequency();
  11. uAST.mangle_names();
  12. }
  13. // 转回 SpiderMonkey AST
  14. return uAST.to_mozilla_ast();
  15. }

原博文有更多细节。