From 1abe14296e9a11d30935dba3f27682a3e67885f8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 8 Oct 2017 13:17:48 +0800 Subject: [PATCH 1/9] collapse `a.b` whenever safe (#2350) --- lib/compress.js | 2 +- test/compress/collapse_vars.js | 71 +++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index d4a72d74..33b4ef00 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -812,7 +812,7 @@ merge(Compressor.prototype, { var sym; if (node instanceof AST_Call || node instanceof AST_Exit - || node instanceof AST_PropAccess + || node instanceof AST_PropAccess && node.has_side_effects(compressor) || node instanceof AST_SymbolRef && (lvalues[node.name] || side_effects && !references_in_scope(node.definition())) diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index ecd18610..631f5b29 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1834,9 +1834,9 @@ issue_1858: { } expect: { console.log(function(x) { - var a = {}, b = a.b = x; + var a = {}, b = a.b = 1; return a.b + b; - }(1)); + }()); } expect_stdout: "2" } @@ -2521,3 +2521,70 @@ issue_2319_3: { } expect_stdout: "true" } + +prop_side_effects_1: { + options = { + collapse_vars: true, + evaluate: true, + pure_getters: "strict", + reduce_vars: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var C = 1; + console.log(C); + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log(1); + console.log({ + bar: function() { + return 2; + } + }.bar()); + } + expect_stdout: [ + "1", + "2", + ] +} + +prop_side_effects_2: { + options = { + collapse_vars: true, + evaluate: true, + inline: true, + passes: 2, + pure_getters: "strict", + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var C = 1; + console.log(C); + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log(1); + console.log(2); + } + expect_stdout: [ + "1", + "2", + ] +} From b810e2f8da4bfd42a8876b64d067e83dfd340aa1 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 9 Oct 2017 12:25:06 +0800 Subject: [PATCH 2/9] perform `reduce_vars` on safe literals (#2351) - constant expression - single reference - same scope - not across loop body --- lib/compress.js | 59 ++++++++-- test/compress/collapse_vars.js | 44 +++++--- test/compress/drop-unused.js | 12 +- test/compress/evaluate.js | 14 ++- test/compress/reduce_vars.js | 193 ++++++++++++++++++++++++++++++++- 5 files changed, 286 insertions(+), 36 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 33b4ef00..c3876f9d 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -297,6 +297,8 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) d.references.push(node); d.fixed = false; }); + var in_loop = null; + var loop_ids = Object.create(null); var tw = new TreeWalker(function(node, descend) { node._squeezed = false; node._optimized = false; @@ -307,7 +309,7 @@ merge(Compressor.prototype, { var d = node.definition(); d.references.push(node); if (d.fixed === undefined || !safe_to_read(d) - || is_modified(node, 0, is_immutable(node.fixed_value()))) { + || is_modified(node, 0, is_immutable(node))) { d.fixed = false; } else { var parent = tw.parent(); @@ -329,6 +331,7 @@ merge(Compressor.prototype, { d.fixed = function() { return node.value; }; + loop_ids[d.id] = in_loop; mark(d, false); descend(); } else { @@ -384,6 +387,7 @@ merge(Compressor.prototype, { d.fixed = function() { return iife.args[i] || make_node(AST_Undefined, iife); }; + loop_ids[d.id] = in_loop; mark(d, true); } else { d.fixed = false; @@ -431,10 +435,13 @@ merge(Compressor.prototype, { return true; } if (node instanceof AST_DWLoop) { + var saved_loop = in_loop; + in_loop = node; push(); node.condition.walk(tw); node.body.walk(tw); pop(); + in_loop = saved_loop; return true; } if (node instanceof AST_LabeledStatement) { @@ -445,6 +452,8 @@ merge(Compressor.prototype, { } if (node instanceof AST_For) { if (node.init) node.init.walk(tw); + var saved_loop = in_loop; + in_loop = node; if (node.condition) { push(); node.condition.walk(tw); @@ -458,14 +467,18 @@ merge(Compressor.prototype, { node.step.walk(tw); pop(); } + in_loop = saved_loop; return true; } if (node instanceof AST_ForIn) { node.init.walk(suppressor); node.object.walk(tw); + var saved_loop = in_loop; + in_loop = node; push(); node.body.walk(tw); pop(); + in_loop = saved_loop; return true; } if (node instanceof AST_Try) { @@ -535,10 +548,23 @@ merge(Compressor.prototype, { } def.references = []; def.should_replace = undefined; + def.single_use = undefined; } - function is_immutable(value) { - return value && value.is_constant() || value instanceof AST_Lambda; + function is_immutable(node) { + var value = node.fixed_value(); + if (!value) return false; + if (value.is_constant()) return true; + if (compressor.option("unused")) { + var d = node.definition(); + if (d.single_use === undefined) { + d.single_use = loop_ids[d.id] === in_loop + && d.scope === node.scope + && value.is_constant_expression(); + } + if (d.references.length == 1 && d.single_use) return true; + } + return value instanceof AST_Lambda; } function is_modified(node, level, immutable) { @@ -2115,6 +2141,22 @@ merge(Compressor.prototype, { } def(AST_Node, return_false); def(AST_Constant, return_true); + def(AST_Function, function(){ + var self = this; + var result = true; + self.walk(new TreeWalker(function(node) { + if (!result) return true; + if (node instanceof AST_SymbolRef) { + var def = node.definition(); + if (self.enclosed.indexOf(def) >= 0 + && self.variables.get(def.name) !== def) { + result = false; + return true; + } + } + })); + return result; + }); def(AST_Unary, function(){ return this.expression.is_constant_expression(); }); @@ -4078,12 +4120,13 @@ merge(Compressor.prototype, { d.fixed = fixed = make_node(AST_Function, fixed, fixed); } if (compressor.option("unused") - && fixed instanceof AST_Function && d.references.length == 1 - && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) - && !d.scope.uses_eval - && compressor.find_parent(AST_Scope) === fixed.parent_scope) { - return fixed.clone(true); + && (d.single_use || fixed instanceof AST_Function + && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg) + && !d.scope.uses_eval + && compressor.find_parent(AST_Scope) === fixed.parent_scope)) { + var value = fixed.optimize(compressor); + return value === fixed ? fixed.clone(true) : value; } if (compressor.option("evaluate") && fixed) { if (d.should_replace === undefined) { diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js index 631f5b29..7d66f7c6 100644 --- a/test/compress/collapse_vars.js +++ b/test/compress/collapse_vars.js @@ -1268,22 +1268,21 @@ collapse_vars_short_circuited_conditions: { collapse_vars_regexp: { options = { - collapse_vars: true, - loops: false, - sequences: true, - dead_code: true, - conditionals: true, - comparisons: true, - evaluate: true, booleans: true, - unused: true, - hoist_funs: true, - keep_fargs: true, + cascade: true, + collapse_vars: true, + comparisons: true, + conditionals: true, + dead_code: true, + evaluate: true, if_return: true, join_vars: true, - cascade: true, - side_effects: true, + hoist_funs: true, + keep_fargs: true, + loops: false, reduce_vars: true, + side_effects: true, + unused: true, } input: { function f1() { @@ -1292,12 +1291,12 @@ collapse_vars_regexp: { return [rx, k]; } function f2() { - var rx = /[abc123]+/; + var rx = /ab*/g; return function(s) { return rx.exec(s); }; } - (function(){ + (function() { var result; var s = 'acdabcdeabbb'; var rx = /ab*/g; @@ -1305,22 +1304,35 @@ collapse_vars_regexp: { console.log(result[0]); } })(); + (function() { + var result; + var s = 'acdabcdeabbb'; + var rx = f2(); + while (result = rx(s)) { + console.log(result[0]); + } + })(); } expect: { function f1() { return [/[A-Z]+/, 9]; } function f2() { - var rx = /[abc123]+/; + var rx = /ab*/g; return function(s) { return rx.exec(s); }; } - (function(){ + (function() { var result, rx = /ab*/g; while (result = rx.exec("acdabcdeabbb")) console.log(result[0]); })(); + (function() { + var result, rx = f2(); + while (result = rx("acdabcdeabbb")) + console.log(result[0]); + })(); } expect_stdout: true } diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index c9048540..4ce8d2eb 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -751,12 +751,12 @@ issue_1583: { expect: { function m(t) { (function(e) { - t = e(); - })(function() { - return (function(a) { - return a; - })(function(a) {}); - }); + t = function() { + return (function(a) { + return function(a) {}; + })(); + }(); + })(); } } } diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 1c737060..5f5a4a93 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -1021,6 +1021,7 @@ issue_1964_1: { input: { function f() { var long_variable_name = /\s/; + console.log(long_variable_name.source); return "a b c".split(long_variable_name)[1]; } console.log(f()); @@ -1028,11 +1029,15 @@ issue_1964_1: { expect: { function f() { var long_variable_name = /\s/; + console.log(long_variable_name.source); return "a b c".split(long_variable_name)[1]; } console.log(f()); } - expect_stdout: "b" + expect_stdout: [ + "\\s", + "b", + ] } issue_1964_2: { @@ -1045,17 +1050,22 @@ issue_1964_2: { input: { function f() { var long_variable_name = /\s/; + console.log(long_variable_name.source); return "a b c".split(long_variable_name)[1]; } console.log(f()); } expect: { function f() { + console.log(/\s/.source); return "a b c".split(/\s/)[1]; } console.log(f()); } - expect_stdout: "b" + expect_stdout: [ + "\\s", + "b", + ] } array_slice_index: { diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 4e096d9d..c1da2991 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -172,6 +172,7 @@ unsafe_evaluate: { options = { evaluate : true, reduce_vars : true, + side_effects : true, unsafe : true, unused : true } @@ -1898,10 +1899,7 @@ redefine_farg_3: { console.log(f([]), g([]), h([])); } expect: { - console.log(function(a) { - var a; - return typeof a; - }([]), "number", "undefined"); + console.log(typeof [], "number", "undefined"); } expect_stdout: "object number undefined" } @@ -2629,3 +2627,190 @@ for_in_prop: { } expect_stdout: "1" } + +obj_var_1: { + options = { + evaluate: true, + passes: 2, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var C = 1; + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log({ + bar: function() { + return 2; + } + }.bar()); + } + expect_stdout: "2" +} + +obj_var_2: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var C = 1; + var obj = { + bar: function() { + return C + C; + } + }; + console.log(obj.bar()); + } + expect: { + console.log(2); + } + expect_stdout: "2" +} + +obj_arg_1: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var C = 1; + function f(obj) { + return obj.bar(); + } + console.log(f({ + bar: function() { + return C + C; + } + })); + } + expect: { + console.log({ + bar: function() { + return 2; + } + }.bar()); + } + expect_stdout: "2" +} + +obj_arg_2: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unsafe: true, + unused: true, + } + input: { + var C = 1; + function f(obj) { + return obj.bar(); + } + console.log(f({ + bar: function() { + return C + C; + } + })); + } + expect: { + console.log(2); + } + expect_stdout: "2" +} + +func_arg_1: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = 42; + !function(a) { + console.log(a()); + }(function() { + return a; + }); + } + expect: { + console.log(42); + } + expect_stdout: "42" +} + +func_arg_2: { + options = { + evaluate: true, + inline: true, + passes: 2, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = 42; + !function(a) { + console.log(a()); + }(function(a) { + return a; + }); + } + expect: { + console.log(void 0); + } + expect_stdout: "undefined" +} + +regex_loop: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function f(x) { + for (var r, s = "acdabcdeabbb"; r = x().exec(s);) + console.log(r[0]); + } + var a = /ab*/g; + f(function() { + return a; + }); + } + expect: { + var a = /ab*/g; + (function(x) { + for (var r, s = "acdabcdeabbb"; r = x().exec(s);) + console.log(r[0]); + })(function() { + return a; + }); + } + expect_stdout: true +} From 70d56c951a7dc0d453261c8cdcf21500728f98d7 Mon Sep 17 00:00:00 2001 From: Tim Malone Date: Wed, 11 Oct 2017 22:48:43 +1100 Subject: [PATCH 3/9] Update README.md - sourceMappingURL directive note (#2355) Moves this README note to underneath the 'url' rather than 'root' option. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6bfede08..f28f93ed 100644 --- a/README.md +++ b/README.md @@ -153,10 +153,10 @@ Additional options: - `--source-map "filename=''"` to specify the name of the source map. - `--source-map "root=''"` to pass the URL where the original files can be found. - Otherwise UglifyJS assumes HTTP `X-SourceMap` is being used and will omit the - `//# sourceMappingURL=` directive. - `--source-map "url=''"` to specify the URL where the source map can be found. + Otherwise UglifyJS assumes HTTP `X-SourceMap` is being used and will omit the + `//# sourceMappingURL=` directive. For example: From 99800d4aa91df31ce449781aaa7d4bc1ae5a0e68 Mon Sep 17 00:00:00 2001 From: Roger Peppe Date: Wed, 11 Oct 2017 19:56:02 +0100 Subject: [PATCH 4/9] update README to include defaults (#2356) fixes #2353 --- README.md | 238 +++++++++++++++++++++++++++++------------------------- 1 file changed, 129 insertions(+), 109 deletions(-) diff --git a/README.md b/README.md index f28f93ed..b57056ee 100644 --- a/README.md +++ b/README.md @@ -203,11 +203,9 @@ Example: To enable the mangler you need to pass `--mangle` (`-m`). The following (comma-separated) options are supported: -- `toplevel` — mangle names declared in the top level scope (disabled by - default). +- `toplevel` (default `false`) -- mangle names declared in the top level scope. -- `eval` — mangle names visible in scopes where `eval` or `with` are used - (disabled by default). +- `eval` (default `false`) -- mangle names visible in scopes where `eval` or `with` are used. When mangling is enabled but you want to prevent certain names from being mangled, you can declare those names with `--mangle reserved` — pass a @@ -590,111 +588,80 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u ## Parse options - `bare_returns` (default `false`) -- support top level `return` statements + - `html5_comments` (default `true`) + - `shebang` (default `true`) -- support `#!command` as the first line ## Compress options -- `sequences` (default: true) -- join consecutive simple statements using the - comma operator. May be set to a positive integer to specify the maximum number - of consecutive comma sequences that will be generated. If this option is set to - `true` then the default `sequences` limit is `200`. Set option to `false` or `0` - to disable. The smallest `sequences` length is `2`. A `sequences` value of `1` - is grandfathered to be equivalent to `true` and as such means `200`. On rare - occasions the default sequences limit leads to very slow compress times in which - case a value of `20` or less is recommended. +- `booleans` (default: `true`) -- various optimizations for boolean context, for example `!!a + ? b : c → a ? b : c` -- `properties` -- rewrite property access using the dot notation, for - example `foo["bar"] → foo.bar` +- `cascade` (default: `true`) -- small optimization for sequences, transform `x, x` into `x` + and `x = something(), x` into `x = something()` -- `dead_code` -- remove unreachable code +- `collapse_vars` (default: `true`) -- Collapse single-use non-constant variables - side + effects permitting. -- `drop_debugger` -- remove `debugger;` statements - -- `unsafe` (default: false) -- apply "unsafe" transformations (discussion below) - -- `unsafe_comps` (default: false) -- Reverse `<` and `<=` to `>` and `>=` to - allow improved compression. This might be unsafe when an at least one of two - operands is an object with computed values due the use of methods like `get`, - or `valueOf`. This could cause change in execution order after operands in the - comparison are switching. Compression only works if both `comparisons` and - `unsafe_comps` are both set to true. - -- `unsafe_Func` (default: false) -- compress and mangle `Function(args, code)` - when both `args` and `code` are string literals. - -- `unsafe_math` (default: false) -- optimize numerical expressions like - `2 * x * 3` into `6 * x`, which may give imprecise floating point results. - -- `unsafe_proto` (default: false) -- optimize expressions like - `Array.prototype.slice.call(a)` into `[].slice.call(a)` - -- `unsafe_regexp` (default: false) -- enable substitutions of variables with - `RegExp` values the same way as if they are constants. - -- `conditionals` -- apply optimizations for `if`-s and conditional - expressions - -- `comparisons` -- apply certain optimizations to binary nodes, for example: +- `comparisons` (default: `true`) -- apply certain optimizations to binary nodes, for example: `!(a <= b) → a > b` (only when `unsafe_comps`), attempts to negate binary nodes, e.g. `a = !b && !c && !d && !e → a=!(b||c||d||e)` etc. -- `evaluate` -- attempt to evaluate constant expressions +- `conditionals` (default: `true`) -- apply optimizations for `if`-s and conditional + expressions -- `booleans` -- various optimizations for boolean context, for example `!!a - ? b : c → a ? b : c` +- `dead_code` (default: `true`) -- remove unreachable code -- `typeofs` -- default `true`. Transforms `typeof foo == "undefined"` into - `foo === void 0`. Note: recommend to set this value to `false` for IE10 and - earlier versions due to known issues. +- `drop_console` (default: `false`) -- default `false`. Pass `true` to discard calls to + `console.*` functions. If you wish to drop a specific function call + such as `console.info` and/or retain side effects from function arguments + after dropping the function call then use `pure_funcs` instead. -- `loops` -- optimizations for `do`, `while` and `for` loops when we can - statically determine the condition +- `drop_debugger` (default: `true`) -- remove `debugger;` statements -- `unused` -- drop unreferenced functions and variables (simple direct variable - assignments do not count as references unless set to `"keep_assign"`) +- `evaluate` (default: `true`) -- attempt to evaluate constant expressions -- `toplevel` -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) - in the top level scope (`false` by default, `true` to drop both unreferenced - functions and variables) +- `expression` (default: `false`) -- default `false`. Pass `true` to preserve completion values + from terminal statements without `return`, e.g. in bookmarklets. -- `top_retain` -- prevent specific toplevel functions and variables from `unused` - removal (can be array, comma-separated, RegExp or function. Implies `toplevel`) +- `hoist_funs` (default: `true`) -- hoist function declarations -- `hoist_funs` -- hoist function declarations - -- `hoist_vars` (default: false) -- hoist `var` declarations (this is `false` +- `hoist_vars` (default: `false`) -- hoist `var` declarations (this is `false` by default because it seems to increase the size of the output in general) -- `if_return` -- optimizations for if/return and if/continue +- `if_return` (default: `true`) -- optimizations for if/return and if/continue -- `inline` -- embed simple functions +- `inline` (default: `true`) -- embed simple functions -- `join_vars` -- join consecutive `var` statements +- `join_vars` (default: `true`) -- join consecutive `var` statements -- `cascade` -- small optimization for sequences, transform `x, x` into `x` - and `x = something(), x` into `x = something()` +- `keep_fargs` (default: `true`) -- default `true`. Prevents the + compressor from discarding unused function arguments. You need this + for code which relies on `Function.length`. -- `collapse_vars` -- Collapse single-use non-constant variables - side - effects permitting. +- `keep_fnames` (default: `false`) -- default `false`. Pass `true` to prevent the + compressor from discarding function names. Useful for code relying on + `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). -- `reduce_vars` -- Improve optimization on variables assigned with and - used as constant values. +- `keep_infinity` (default: `false`) -- default `false`. Pass `true` to prevent `Infinity` from + being compressed into `1/0`, which may cause performance issues on Chrome. -- `warnings` -- display warnings when dropping unreachable code or unused - declarations etc. +- `loops` (default: `true`) -- optimizations for `do`, `while` and `for` loops when we can + statically determine the condition -- `negate_iife` -- negate "Immediately-Called Function Expressions" +- `negate_iife` (default: `true`) -- negate "Immediately-Called Function Expressions" where the return value is discarded, to avoid the parens that the code generator would insert. -- `pure_getters` -- the default is `false`. If you pass `true` for - this, UglifyJS will assume that object property access - (e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. - Specify `"strict"` to treat `foo.bar` as side-effect-free only when - `foo` is certain to not throw, i.e. not `null` or `undefined`. +- `passes` (default: `1`) -- The maximum number of times to run compress. + In some cases more than one pass leads to further compressed code. Keep in + mind more passes will take more time. -- `pure_funcs` -- default `null`. You can pass an array of names and +- `properties` (default: `true`) -- rewrite property access using the dot notation, for + example `foo["bar"] → foo.bar` + +- `pure_funcs` (default: `null`) -- You can pass an array of names and UglifyJS will assume that those functions do not produce side effects. DANGER: will not check if the name is redefined in scope. An example case here, for instance `var q = Math.floor(a/b)`. If @@ -705,34 +672,67 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u statement would get discarded. The current implementation adds some overhead (compression will be slower). -- `drop_console` -- default `false`. Pass `true` to discard calls to - `console.*` functions. If you wish to drop a specific function call - such as `console.info` and/or retain side effects from function arguments - after dropping the function call then use `pure_funcs` instead. +- `pure_getters` (default: `"strict"`) -- If you pass `true` for + this, UglifyJS will assume that object property access + (e.g. `foo.bar` or `foo["bar"]`) doesn't have any side effects. + Specify `"strict"` to treat `foo.bar` as side-effect-free only when + `foo` is certain to not throw, i.e. not `null` or `undefined`. -- `expression` -- default `false`. Pass `true` to preserve completion values - from terminal statements without `return`, e.g. in bookmarklets. +- `reduce_vars` (default: `true`) -- Improve optimization on variables assigned with and + used as constant values. -- `keep_fargs` -- default `true`. Prevents the - compressor from discarding unused function arguments. You need this - for code which relies on `Function.length`. +- `sequences` (default: `true`) -- join consecutive simple statements using the + comma operator. May be set to a positive integer to specify the maximum number + of consecutive comma sequences that will be generated. If this option is set to + `true` then the default `sequences` limit is `200`. Set option to `false` or `0` + to disable. The smallest `sequences` length is `2`. A `sequences` value of `1` + is grandfathered to be equivalent to `true` and as such means `200`. On rare + occasions the default sequences limit leads to very slow compress times in which + case a value of `20` or less is recommended. -- `keep_fnames` -- default `false`. Pass `true` to prevent the - compressor from discarding function names. Useful for code relying on - `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). - -- `passes` -- default `1`. The maximum number of times to run compress. - In some cases more than one pass leads to further compressed code. Keep in - mind more passes will take more time. - -- `keep_infinity` -- default `false`. Pass `true` to prevent `Infinity` from - being compressed into `1/0`, which may cause performance issues on Chrome. - -- `side_effects` -- default `true`. Pass `false` to disable potentially dropping +- `side_effects` (default: `true`) -- default `true`. Pass `false` to disable potentially dropping functions marked as "pure". A function call is marked as "pure" if a comment annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: `/*@__PURE__*/foo();` +- `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) + in the top level scope (`false` by default, `true` to drop both unreferenced + functions and variables) + +- `top_retain` (default: `null`) -- prevent specific toplevel functions and variables from `unused` + removal (can be array, comma-separated, RegExp or function. Implies `toplevel`) + +- `typeofs` (default: `true`) -- default `true`. Transforms `typeof foo == "undefined"` into + `foo === void 0`. Note: recommend to set this value to `false` for IE10 and + earlier versions due to known issues. + +- `unsafe_comps` (default: `false`) -- Reverse `<` and `<=` to `>` and `>=` to + allow improved compression. This might be unsafe when an at least one of two + operands is an object with computed values due the use of methods like `get`, + or `valueOf`. This could cause change in execution order after operands in the + comparison are switching. Compression only works if both `comparisons` and + `unsafe_comps` are both set to true. + +- `unsafe` (default: `false`) -- apply "unsafe" transformations (discussion below) + +- `unsafe_Func` (default: `false`) -- compress and mangle `Function(args, code)` + when both `args` and `code` are string literals. + +- `unsafe_math` (default: `false`) -- optimize numerical expressions like + `2 * x * 3` into `6 * x`, which may give imprecise floating point results. + +- `unsafe_proto` (default: `false`) -- optimize expressions like + `Array.prototype.slice.call(a)` into `[].slice.call(a)` + +- `unsafe_regexp` (default: `false`) -- enable substitutions of variables with + `RegExp` values the same way as if they are constants. + +- `unused` (default: `true`) -- drop unreferenced functions and variables (simple direct variable + assignments do not count as references unless set to `"keep_assign"`) + +- `warnings` (default: `false`) -- display warnings when dropping unreachable code or unused + declarations etc. + ## Mangle options - `reserved` (default `[]`). Pass an array of identifiers that should be @@ -772,16 +772,20 @@ UglifyJS.minify(code, { mangle: { toplevel: true } }).code; ### Mangle properties options -- `reserved` (default: `[]`) -- Do not mangle property names listed in the - `reserved` array. -- `regex` (default: `null`) -— Pass a RegExp literal to only mangle property - names matching the regular expression. -- `keep_quoted` (default: `false`) -— Only mangle unquoted property names. -- `debug` (default: `false`) -— Mangle names with the original name still present. - Pass an empty string `""` to enable, or a non-empty string to set the debug suffix. - `builtins` (default: `false`) -- Use `true` to allow the mangling of builtin DOM properties. Not recommended to override this setting. +- `debug` (default: `false`) -— Mangle names with the original name still present. + Pass an empty string `""` to enable, or a non-empty string to set the debug suffix. + +- `keep_quoted` (default: `false`) -— Only mangle unquoted property names. + +- `regex` (default: `null`) -— Pass a RegExp literal to only mangle property + names matching the regular expression. + +- `reserved` (default: `[]`) -- Do not mangle property names listed in the + `reserved` array. + ## Output options The code generator tries to output shortest code possible by default. In @@ -790,31 +794,43 @@ can pass additional arguments that control the code output: - `ascii_only` (default `false`) -- escape Unicode characters in strings and regexps (affects directives with non-ascii characters becoming invalid) + - `beautify` (default `true`) -- whether to actually beautify the output. Passing `-b` will set this to true, but you might need to pass `-b` even when you want to generate minified code, in order to specify additional arguments, so you can use `-b beautify=false` to override it. + - `bracketize` (default `false`) -- always insert brackets in `if`, `for`, `do`, `while` or `with` statements, even if their body is a single statement. + - `comments` (default `false`) -- pass `true` or `"all"` to preserve all comments, `"some"` to preserve some comments, a regular expression string (e.g. `/^!/`) or a function. -- `indent_level` (default 4) -- `indent_start` (default 0) -- prefix all lines by that many spaces + +- `indent_level` (default `4`) + +- `indent_start` (default `0`) -- prefix all lines by that many spaces + - `inline_script` (default `false`) -- escape the slash in occurrences of ` Date: Thu, 12 Oct 2017 02:58:25 +0800 Subject: [PATCH 5/9] more tests for #2351 (#2357) --- test/compress/reduce_vars.js | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index c1da2991..d93f0cd1 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2814,3 +2814,83 @@ regex_loop: { } expect_stdout: true } + +obj_for_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { a: 1 }; + for (var i = o.a--; i; i--) + console.log(i); + } + expect: { + for (var i = { a: 1 }.a--; i; i--) + console.log(i); + } + expect_stdout: "1" +} + +obj_for_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var o = { a: 1 }; + for (var i; i = o.a--;) + console.log(i); + } + expect: { + var o = { a: 1 }; + for (var i; i = o.a--;) + console.log(i); + } + expect_stdout: "1" +} + +array_forin_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = [ 1, 2, 3 ]; + for (var b in a) + console.log(b); + } + expect: { + for (var b in [ 1, 2, 3 ]) + console.log(b); + } + expect_stdout: [ + "0", + "1", + "2", + ] +} + +array_forin_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = []; + for (var b in [ 1, 2, 3 ]) + a.push(b); + console.log(a.length); + } + expect: { + var a = []; + for (var b in [ 1, 2, 3 ]) + a.push(b); + console.log(a.length); + } + expect_stdout: "3" +} From ec598c351b25788334efb1fb35415c53eb0414e0 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 15 Oct 2017 22:33:55 +0800 Subject: [PATCH 6/9] fix-ups for #2356 (#2360) --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b57056ee..64bbafc4 100644 --- a/README.md +++ b/README.md @@ -508,6 +508,9 @@ if (result.error) throw result.error; - `ie8` (default `false`) - set to `true` to support IE8. +- `keep_fnames` (default: `false`) - pass `true` to prevent discarding or mangling + of function names. Useful for code relying on `Function.prototype.name`. + ## Minify options structure ```javascript @@ -625,6 +628,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `expression` (default: `false`) -- default `false`. Pass `true` to preserve completion values from terminal statements without `return`, e.g. in bookmarklets. +- `global_defs` (default: `{}`) -- see [conditional compilation](#conditional-compilation) + - `hoist_funs` (default: `true`) -- hoist function declarations - `hoist_vars` (default: `false`) -- hoist `var` declarations (this is `false` @@ -640,10 +645,6 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u compressor from discarding unused function arguments. You need this for code which relies on `Function.length`. -- `keep_fnames` (default: `false`) -- default `false`. Pass `true` to prevent the - compressor from discarding function names. Useful for code relying on - `Function.prototype.name`. See also: the `keep_fnames` [mangle option](#mangle). - - `keep_infinity` (default: `false`) -- default `false`. Pass `true` to prevent `Infinity` from being compressed into `1/0`, which may cause performance issues on Chrome. @@ -695,6 +696,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For example: `/*@__PURE__*/foo();` +- `switches` (default: `true`) -- de-duplicate and remove unreachable `switch` branches + - `toplevel` (default: `false`) -- drop unreferenced functions (`"funcs"`) and/or variables (`"vars"`) in the top level scope (`false` by default, `true` to drop both unreferenced functions and variables) @@ -741,10 +744,6 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u - `toplevel` (default `false`). Pass `true` to mangle names declared in the top level scope. -- `keep_fnames` (default `false`). Pass `true` to not mangle function names. - Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames` - [compress option](#compress-options). - - `eval` (default `false`). Pass `true` to mangle names visible in scopes where `eval` or `with` are used. From a09c8ad6661f093b65761ac6a6c0bb00107ed8a8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 16 Oct 2017 02:41:22 +0800 Subject: [PATCH 7/9] update dependency (#2362) - source-map@0.6.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index da570ada..183dba51 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ ], "dependencies": { "commander": "~2.11.0", - "source-map": "~0.5.1" + "source-map": "~0.6.1" }, "devDependencies": { "acorn": "~5.1.1", From dfe4f6c6de3971e48b6a5a807e17314d65cdc9b5 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Mon, 16 Oct 2017 02:44:17 +0800 Subject: [PATCH 8/9] v3.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 183dba51..d40030bf 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "http://lisperator.net/uglifyjs", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.1.3", + "version": "3.1.4", "engines": { "node": ">=0.8.0" }, From d8ee2de95cf5800dd3c5e869f70d8e716410f745 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Mon, 16 Oct 2017 12:37:20 +0800 Subject: [PATCH 9/9] adjust tests for #2351 --- test/compress/reduce_vars.js | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index e8829483..0c758c0b 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -2933,7 +2933,9 @@ obj_for_1: { console.log(i); } expect: { - for (var i = { a: 1 }.a--; i; i--) + // TODO make this work on harmony + var o = { a: 1 }; + for (var i = o.a--; i; i--) console.log(i); } expect_stdout: "1" @@ -2970,7 +2972,9 @@ array_forin_1: { console.log(b); } expect: { - for (var b in [ 1, 2, 3 ]) + // TODO make this work on harmony + var a = [ 1, 2, 3 ]; + for (var b in a) console.log(b); } expect_stdout: [ @@ -3000,3 +3004,50 @@ array_forin_2: { } expect_stdout: "3" } + +array_forof_1: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = [ 1, 2, 3 ]; + for (var b of a) + console.log(b); + } + expect: { + // TODO make this work on harmony + var a = [ 1, 2, 3 ]; + for (var b of a) + console.log(b); + } + expect_stdout: [ + "1", + "2", + "3", + ] + node_version: ">=0.12" +} + +array_forof_2: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = []; + for (var b of [ 1, 2, 3 ]) + a.push(b); + console.log(a.length); + } + expect: { + var a = []; + for (var b of [ 1, 2, 3 ]) + a.push(b); + console.log(a.length); + } + expect_stdout: "3" + node_version: ">=0.12" +}