使用
uglifyjs [input files] [options]
UglifyJS2可以输入多文件。建议你先写输入文件,再传选项。UglifyJS会根据压缩选项,把文件放在队列中依次解释。所有文件都会在同一个全局域中,假如一个文件中的变量、方法被另一文件引用,UglifyJS会合理地匹配。
假如你不要输入文件,而是要输入字符串(STDIN),那就把文件名换成一个横线(-)
如果你想要把选项写在文件名的前面,那要在二者之前加上双横线,防止文件名被当成了选项:
uglifyjs --compress --mangle -- input.js
以下是可用的选项:
--source-map 指定输出的文件产生一份sourcemap
--source-map-root 此路径中的源码编译后会产生sourcemap
--source-map-url 放在//#sourceMappingURL的sourcemap路径. 默认是
--source-map传入的值.
--source-map-include-sources 如果你要在sourcemap中加上源文件的内容作为sourcesContent属性,
就传这个参数吧。
--source-map-inline 把sourcemap以base64格式附在输出文件结尾
--in-source-map 输入sourcemap。假如的你要编译的JS是另外的源码编译出来的。
假如该sourcemap包含在js内,请指定"inline"。
--screw-ie8 是否要支持IE6/7/8。UglifyJS默认不兼容IE。
--support-ie8 是否要支持IE6/7/8,等同于在`compress`, `mangle` 和
`output`选项中都设置`screw_ie8: false`
--expr 编译一个表达式,而不是编译一段代码(编译JSON时用)
-p, --prefix 忽略sourcemap中源码的前缀。例如`-p 3`会干掉文件名前面3层目录
以及保证路径是相对路径。你也可以指定`-p relative`,让UglifyJS
自己计算输出文件、sourcemap与源码之间的相对路径。
-o, --output 输出文件,默认标准输出(STDOUT)
-b, --beautify 美化输出/指定输出 选项
-m, --mangle Mangle的名字,或传入一个mangler选项.
-r, --reserved mangle的例外,不包含在mangling的名字
-c, --compress 是否启用压缩功能(true/fasle),或者传一个压缩选项对象, 例如
`-c 'if_return=false,pure_funcs=["Math.pow","console.log"]'`,
`-c`不带参数的话就是用默认的压缩设置。
-d, --define 全局定义
-e, --enclose 所有代码嵌入到一个大方法中,传入参数为配置项
--comments 保留版权注释。默认保留Google Closure那样的,保留JSDoc-style、
包含"@license" 或"@preserve"字样的注释。你也可以传下面的参数:
- "all" 保留所有注释
- 正则(如`/foo/`、`/^!/`)保留匹配到的。要注意,如果启用了压
缩,因为会移除不可达代码以及压缩连续声明,因此不是*所有*注释都能
保留下来。
--preamble 在输出文件开头插入的前言。你可以插入一段注释,例如版权信息。
这些不会被编译,但sourcemap会改成当前的样子。
--stats 在STDERR中显示操作运行时间。
--acorn 用 Acorn解析。
--spidermonkey 假如输入文件是 SpiderMonkey AST 格式(像JSON).
--self 把UglifyJS2本身也构建成一个依赖包
(等同于`--wrap=UglifyJS --export-all`)
--wrap 所有代码嵌入到一个大函数中,让"exports"和"global"变量有效,
你需要传入一个参数指定模块被浏览器引入时的名字。
--export-all 只当`--wrap`时有效,告诉UglifyJS自动把代码暴露到全局。
--lint 显示一些可视警告
-v, --verbose Verbose
-V, --version 打印版本号.
--noerr 不要为-c,-b 或 -m选项中出现未知选项而抛出错误。
--bare-returns 允许返回函数的外部。当最小化CommonJs模块和Userscripts时,
可能匿名函数会被.user.js引擎调用立即执行(IIFE)
--keep-fnames 不要混淆、干掉的函数的名字。当代码依赖Function.prototype.name时有用。
--reserved-file 要保留的文件的名字
--reserve-domprops 保留(绝大部分?)DOM的属性,当--mangle-props
--mangle-props 混淆属性,默认是`0`.设置为`true`或`1`则会混淆所有属性名。
设为`unquoted`或 `2`则只混淆不在引号内的属性。`2`时也会让
`keep_quoted_props` 美化选项生效,保留括号内的属性;让压缩选项
的`properties`失效,阻止覆写带点号(.)的属性。你可以通过在命令
中明确设置来覆写它们。
--mangle-regex 混淆正则,只混淆匹配到的属性名。
--name-cache 用来保存混淆map的文件
--pure-funcs 假如返回值没被调用则可以安全移除的函数。
例如`--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中路径的前缀。
例如:
uglifyjs /home/doe/work/foo/src/js/file1.js \
/home/doe/work/foo/src/js/file2.js \
-o foo.min.js \
--source-map foo.min.js.map \
--source-map-root http://foo.com/src \
-p 5 -c -m
上述配置会压缩和混淆file1.js
、file2.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.js
、js/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
- 混淆在eval
或with
作用域出现的变量名(默认disabled)
当启用混淆功能时,如果你希望保留一些名字不被混淆,你可以用--reserved
(-r
) 声明一些名字,用逗号隔开。例如:
uglifyjs ... -m -r '$,require,exports'
防止require
, exports
和 $
被混淆改变。
混淆属性名 (--mangle-props
)
警告:这能会搞崩你的代码。混淆属性名跟混淆变量名不一样,是相互独立的。传入--mangle-props
会混淆对象所有可见的属性名。例如:
var x = {
foo: 1
};
x.bar = 2;
x["baz"] = 3;
x[condition ? "moo" : "boo"] = 4;
console.log(x.something());
上面代码中,foo
, bar
, baz
, moo
、 boo
会被替换成单字符名字,something()
则不变。
为了合理地使用,我们应该避免混淆一些JS标准的名字。比如,如果你代码中有x.length = 10
,那length
就将被混淆,不管这是在对象中还是访问数组的长度,它都被干掉。为了避免这种情况,你可以用 --reserved-file
来输入一个文件,里面包含不参与混淆的名字,变量名或属性名都行。就像下面这样:
{
"vars": [ "define", "require", ... ],
"props": [ "length", "prototype", ... ]
}
--reserved-file
可以是文件名数组(用逗号隔开,你也可以传多个--reserved-file
),在上面例子中的名字将被排除在混淆中。
tools/domprops.json
里有一个默认的排除名单,包括绝大部分标准JS和多种浏览器中的DOM属性名。传入--reserve-domprops
可以读取此名单生效。
你也可以用正则表达式来定义一些应该被混淆的属性名。例如--mangle-regex="/^_/"
,会只混淆以下划线开始的属性名。
当你压缩多个文件时,为了保证让它们最终能同时工作,我们要让他们中同样的属性名混淆成相同的结果。传入--name-cache
filename.json
,UglifyJS会维护一个共同的映射供他们复用。这个json一开始应该是空的,例如:
rm -f /tmp/cache.json # start fresh
uglifyjs file1.js file2.js --mangle-props --name-cache /tmp/cache.json -o part1.js
uglifyjs file3.js file4.js --mangle-props --name-cache /tmp/cache.json -o part2.js
现在,part1.js
和 part2.js
会知晓对方混淆的属性名。
假如你把所有文件压缩成同一个文件,那就不需要启用名字缓存了。
混淆括号中的名字(--mangle-props=unquoted
或 --mangle-props=2
)
使用括号属性名 (o["foo"]
)以保留属性名(foo
)。这会让整个脚本中其余此属性的引用(o.foo
)也不被混淆。例如:
$ echo 'var o={"foo":1, bar:3}; o.foo += o.bar; console.log(o.foo);' | uglifyjs --mangle-props=2 -mc
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
个,设为false
或0
则禁用。sequences
至少要是2
,1
的话等同于true
(即200
)。默认的sequences设置有极小几率会导致压缩很慢,所以推荐设置成20
或以下。properties
— 用.
来重写属性引用,例如foo["bar"] → foo.bar
dead_code
— 移除没被引用的代码drop_debugger
— 移除debugger;
unsafe
(默认 false) — 使用 “unsafe”转换 (下面详述)unsafe_comps
(默认 false) — 保留<
和<=
不被换成>
和>=
。假如某些运算对象是用get
或valueOf
object得出的时候,转换可能会不安全,可能会引起运算对象的改变。此选项只有当comparisons
和unsafe_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
— 当do
、while
、for
循环的判断条件可以确定是,对其进行优化。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/continuejoin_vars
— 合并连续var
声明cascade
— 弱弱地优化一下连续声明, 将x, x
转成x
,x = something(), x
转成x = something()
collapse_vars
— 当var
和const
单独使用时尽量合并reduce_vars
— 优化某些变量实际上是按常量值来赋值、使用的情况。warnings
— 当删除没有用处的代码时,显示警告negate_iife
— 当立即执行函数(IIFE)的返回值没用时,取消之。避免代码生成器会插入括号。pure_getters
— 默认是false
. 如果你传入true
,UglifyJS会假设对象属性的引用(例如foo.bar
或foo["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 0
→undefined
(假如作用域中有一个变量名叫”undefined”;我们这么做是因为变量名会被混淆成单字符)
编译条件语句
Uglify会假设全局变量都是常量(不管是否在局部域中定义了),你可以用--define
(-d
)来实现定义全局变量。例如你传--define DEBUG=false
,UglifyJS会在输出中干掉下面代码:
if (DEBUG) {
console.log("debug stuff");
}
你可以像--define env.DEBUG=false
这样写嵌套的常量。
在干掉那些永否的条件语句以及不可达代码时,UglifyJS会给出警告。现在没有选项可以禁用此特性,但你可以设置 warnings=false
来禁掉所有警告。
另一个定义全局常量的方法是,在一个独立的文档中定义,再引入到构建中。例如你有一个这样的build/defines.js
:
const DEBUG = false;
const PRODUCTION = true;
// 等等
构建使用这样写:
uglifyjs build/defines.js js/foo.js js/bar.js... -c
UglifyJS会注意到这些常量。因为它们无法改变,所以它们会被认为是没被引用而被照样干掉。如果你用const
声明,构建后还会被保留。如果你的运行环境低于ES6、不支持const
,请用var
声明加上reduce_vars
设置(默认启用)来实现。
编译条件语句API
你也可以通过程序API来设置编译配置。其中有差别的是一个压缩器属性global_defs
:
uglifyJS.minify([ "input.js"], {
compress: {
dead_code: true,
global_defs: {
DEBUG: false
}
}
});
美化器选项(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”的注释。
注意,无论如何,总会有些注释在某些情况下会丢失。例如:
function f() {
/** @preserve Foo Bar */
function g() {
// this function is never called
}
return something();
}
即使里面带有”@preserve”,注释依然会被丢弃。因为内部的函数g
(注释所依附的抽象语法树节点)没有被引用、会被压缩器干掉。
书写版权信息(或其他需要在输出文件中保留的信息)的最安全位置是全局节点。
对SpiderMonkey的支持
UglifyJS2有自己的抽象语法树格式;为了某些现实的原因
我们无法在内部轻易地改成使用SpiderMonkey抽象语法树(AST)。但UglifyJS现在有了一个可以输入SpiderMonkeyAST的转换器。
例如[Acorn][acorn] ,这是一个超级快的生成SpiderMonkey AST的解释器。它带有一个实用的迷你CLI,能解释一个文件、把AST转存为JSON并标准输出。可以这样用UglifyJS来压缩混淆:
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格式。
例如:
function uglify(ast, options, mangle) {
// 把SpiderMonkey AST 转成中间格式
var uAST = UglifyJS.AST_Node.from_mozilla_ast(ast);
// 压缩
uAST.figure_out_scope();
uAST = UglifyJS.Compressor(options).compress(uAST);
// 混淆 (可选)
if (mangle) {
uAST.figure_out_scope();
uAST.compute_char_frequency();
uAST.mangle_names();
}
// 转回 SpiderMonkey AST
return uAST.to_mozilla_ast();
}
原博文有更多细节。