Compare commits

...

16 Commits

Author SHA1 Message Date
Alex Lam S.L
bd7be07c38 v3.0.24 2017-07-08 12:53:20 +08:00
Alex Lam S.L
71ee91e716 handle duplicate argument names in collapse_vars (#2215) 2017-07-08 04:42:35 +08:00
Alex Lam S.L
4f70d2e28c inlining of static methods & constants (#2211)
- guard by `unsafe`
- support `Array`, `Math`, `Number`, `Object` & `String`

fixes #2207
2017-07-07 05:35:32 +08:00
Alex Lam S.L
4b6ca5e742 inline property access of object literal (#2209)
- only if property value is side-effect-free
- guard by `unsafe`

fixes #2208
2017-07-06 21:51:58 +08:00
Alex Lam S.L
9306da3c58 suppress collapse_vars of this as call argument (#2204)
fixes #2203
2017-07-06 01:03:52 +08:00
Alex Lam S.L
1ac25fc032 improve compress granularity through typeofs (#2201)
fixes #2198
2017-07-05 19:20:33 +08:00
Alex Lam S.L
5f046c724b minor clean-ups to evaluate (#2197) 2017-07-03 18:52:39 +08:00
Alex Lam S.L
af0262b7e5 improve parenthesis emission (#2196)
- eliminate `throw` usages
- suppress extraneous parenthesis
  - `new function() {foo.bar()}.baz`
  - `for (function() { "foo" in bar; };;);`
2017-07-03 04:17:37 +08:00
Alex Lam S.L
6b3aeff1d8 clean up TreeWalker.pop() (#2195)
Remove superfluous parameter.
2017-07-03 03:23:38 +08:00
Alex Lam S.L
20e4f8277f refactor throw usage within compress (#2193)
Eliminate exceptional constructs from normal control flow.
2017-07-03 02:10:56 +08:00
kzc
f3a487a368 document fast mangle-only minify mode (#2194) 2017-07-03 01:37:04 +08:00
Alex Lam S.L
2dde41615a v3.0.23 2017-07-02 17:24:22 +08:00
Alex Lam S.L
8b69a3d18e drop argument value after collapse_vars (#2190) 2017-07-02 04:28:11 +08:00
Alex Lam S.L
d40950b741 improve inline efficiency (#2188)
... by teaching `collapse_vars` some new tricks.

fixes #2187
2017-07-02 01:05:14 +08:00
Alex Lam S.L
7659ea1d2e v3.0.22 2017-06-30 11:18:34 +08:00
Alex Lam S.L
bdeadffbf5 improve usability of name cache under minify() (#2176)
fixes #2174
2017-06-29 12:48:34 +08:00
22 changed files with 1007 additions and 305 deletions

View File

@@ -111,7 +111,7 @@ a double dash to prevent input files being used as option arguments:
By default UglifyJS will not try to be IE-proof. By default UglifyJS will not try to be IE-proof.
--keep-fnames Do not mangle/drop function names. Useful for --keep-fnames Do not mangle/drop function names. Useful for
code relying on Function.prototype.name. code relying on Function.prototype.name.
--name-cache File to hold mangled name mappings. --name-cache <file> File to hold mangled name mappings.
--self Build UglifyJS as a library (implies --wrap UglifyJS) --self Build UglifyJS as a library (implies --wrap UglifyJS)
--source-map [options] Enable source map/specify source map options: --source-map [options] Enable source map/specify source map options:
`base` Path to compute relative paths from input files. `base` Path to compute relative paths from input files.
@@ -383,7 +383,47 @@ var code = {
var options = { toplevel: true }; var options = { toplevel: true };
var result = UglifyJS.minify(code, options); var result = UglifyJS.minify(code, options);
console.log(result.code); console.log(result.code);
// console.log(function(n,o){return n+o}(3,7)); // console.log(3+7);
```
The `nameCache` option:
```javascript
var options = {
mangle: {
toplevel: true,
},
nameCache: {}
};
var result1 = UglifyJS.minify({
"file1.js": "function add(first, second) { return first + second; }"
}, options);
var result2 = UglifyJS.minify({
"file2.js": "console.log(add(1 + 2, 3 + 4));"
}, options);
console.log(result1.code);
// function n(n,r){return n+r}
console.log(result2.code);
// console.log(n(3,7));
```
You may persist the name cache to the file system in the following way:
```javascript
var cacheFileName = "/tmp/cache.json";
var options = {
mangle: {
properties: true,
},
nameCache: JSON.parse(fs.readFileSync(cacheFileName, "utf8"))
};
fs.writeFileSync("part1.js", UglifyJS.minify({
"file1.js": fs.readFileSync("file1.js", "utf8"),
"file2.js": fs.readFileSync("file2.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync("part2.js", UglifyJS.minify({
"file3.js": fs.readFileSync("file3.js", "utf8"),
"file4.js": fs.readFileSync("file4.js", "utf8")
}, options).code, "utf8");
fs.writeFileSync(cacheFileName, JSON.stringify(options.nameCache), "utf8");
``` ```
An example of a combination of `minify()` options: An example of a combination of `minify()` options:
@@ -461,6 +501,13 @@ if (result.error) throw result.error;
- `toplevel` (default `false`) - set to `true` if you wish to enable top level - `toplevel` (default `false`) - set to `true` if you wish to enable top level
variable and function name mangling and to drop unused variables and functions. variable and function name mangling and to drop unused variables and functions.
- `nameCache` (default `null`) - pass an empty object `{}` or a previously
used `nameCache` object if you wish to cache mangled variable and
property names across multiple invocations of `minify()`. Note: this is
a read/write property. `minify()` will read the name cache state of this
object and update it during minification so that it may be
reused or externally persisted by the user.
- `ie8` (default `false`) - set to `true` to support IE8. - `ie8` (default `false`) - set to `true` to support IE8.
## Minify options structure ## Minify options structure
@@ -487,6 +534,7 @@ if (result.error) throw result.error;
sourceMap: { sourceMap: {
// source map options // source map options
}, },
nameCache: null, // or specify a name cache object
toplevel: false, toplevel: false,
ie8: false, ie8: false,
} }
@@ -596,6 +644,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `booleans` -- various optimizations for boolean context, for example `!!a - `booleans` -- various optimizations for boolean context, for example `!!a
? b : c → a ? b : c` ? b : c → a ? b : c`
- `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.
- `loops` -- optimizations for `do`, `while` and `for` loops when we can - `loops` -- optimizations for `do`, `while` and `for` loops when we can
statically determine the condition statically determine the condition
@@ -825,7 +877,6 @@ when this flag is on:
- `new Object()` → `{}` - `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp` - `String(exp)` or `exp.toString()` → `"" + exp`
- `new Object/RegExp/Function/Error/Array (...)` → we discard the `new` - `new Object/RegExp/Function/Error/Array (...)` → we discard the `new`
- `typeof foo == "undefined"` → `foo === void 0`
- `void 0` → `undefined` (if there is a variable named "undefined" in - `void 0` → `undefined` (if there is a variable named "undefined" in
scope; we do it because the variable name will be mangled, typically scope; we do it because the variable name will be mangled, typically
reduced to a single character) reduced to a single character)
@@ -978,3 +1029,29 @@ in total it's a bit more than just using UglifyJS's own parser.
[acorn]: https://github.com/ternjs/acorn [acorn]: https://github.com/ternjs/acorn
[sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k [sm-spec]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k
### Uglify Fast Minify Mode
It's not well known, but variable and function name 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
Uglify builds by 3 to 4 times. In this fast `mangle`-only mode Uglify has
comparable minify speeds and gzip sizes to
[`butternut`](https://www.npmjs.com/package/butternut):
| d3.js | minify size | gzip size | minify time (seconds) |
| --- | ---: | ---: | ---: |
| original | 451,131 | 108,733 | - |
| uglify-js@3.0.23 mangle=false, compress=false | 316,600 | 85,245 | 0.73 |
| uglify-js@3.0.23 mangle=true, compress=false | 220,216 | 72,730 | 1.21 |
| Butternut 0.4.6 | 217,568 | 72,738 | 1.81 |
| uglify-js@3.0.23 mangle=true, compress=true | 212,511 | 71,560 | 4.64 |
To enable fast minify mode from the CLI use:
```
uglifyjs file.js -m
```
To enable fast minify mode with the API use:
```js
UglifyJS.minify(code, { compress: false, mangle: true });
```

View File

@@ -106,17 +106,8 @@ if (program.mangleProps) {
if (typeof options.mangle != "object") options.mangle = {}; if (typeof options.mangle != "object") options.mangle = {};
options.mangle.properties = program.mangleProps; options.mangle.properties = program.mangleProps;
} }
var cache;
if (program.nameCache) { if (program.nameCache) {
cache = JSON.parse(read_file(program.nameCache, "{}")); options.nameCache = JSON.parse(read_file(program.nameCache, "{}"));
if (options.mangle) {
if (typeof options.mangle != "object") options.mangle = {};
options.mangle.cache = to_cache("vars");
if (options.mangle.properties) {
if (typeof options.mangle.properties != "object") options.mangle.properties = {};
options.mangle.properties.cache = to_cache("props");
}
}
} }
if (program.output == "ast") { if (program.output == "ast") {
options.output = { options.output = {
@@ -266,9 +257,7 @@ function run() {
print(result.code); print(result.code);
} }
if (program.nameCache) { if (program.nameCache) {
fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { fs.writeFileSync(program.nameCache, JSON.stringify(options.nameCache));
return value instanceof UglifyJS.Dictionary ? value.toObject() : value;
}));
} }
if (result.timings) for (var phase in result.timings) { if (result.timings) for (var phase in result.timings) {
print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s");
@@ -381,18 +370,6 @@ function parse_source_map() {
} }
} }
function to_cache(key) {
if (cache[key]) {
cache[key].props = UglifyJS.Dictionary.fromObject(cache[key].props);
} else {
cache[key] = {
cname: -1,
props: new UglifyJS.Dictionary()
};
}
return cache[key];
}
function skip_key(key) { function skip_key(key) {
return skip_keys.indexOf(key) >= 0; return skip_keys.indexOf(key) >= 0;
} }

View File

@@ -859,7 +859,7 @@ TreeWalker.prototype = {
if (!ret && descend) { if (!ret && descend) {
descend.call(node); descend.call(node);
} }
this.pop(node); this.pop();
return ret; return ret;
}, },
parent: function(n) { parent: function(n) {
@@ -873,9 +873,8 @@ TreeWalker.prototype = {
} }
this.stack.push(node); this.stack.push(node);
}, },
pop: function(node) { pop: function() {
this.stack.pop(); if (this.stack.pop() instanceof AST_Lambda) {
if (node instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives); this.directives = Object.getPrototypeOf(this.directives);
} }
}, },

View File

@@ -80,6 +80,7 @@ function Compressor(options, false_by_default) {
switches : !false_by_default, switches : !false_by_default,
top_retain : null, top_retain : null,
toplevel : !!(options && options["top_retain"]), toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false, unsafe : false,
unsafe_comps : false, unsafe_comps : false,
unsafe_Func : false, unsafe_Func : false,
@@ -714,15 +715,23 @@ merge(Compressor.prototype, {
var candidates = []; var candidates = [];
var stat_index = statements.length; var stat_index = statements.length;
while (--stat_index >= 0) { while (--stat_index >= 0) {
// Treat parameters as collapsible in IIFE, i.e.
// function(a, b){ ... }(x());
// would be translated into equivalent assignments:
// var a = x(), b = undefined;
if (stat_index == 0 && compressor.option("unused")) extract_args();
// Find collapsible assignments
extract_candidates(statements[stat_index]); extract_candidates(statements[stat_index]);
while (candidates.length > 0) { while (candidates.length > 0) {
var candidate = candidates.pop(); var candidate = candidates.pop();
var lhs = get_lhs(candidate); var lhs = get_lhs(candidate);
if (!lhs || is_lhs_read_only(lhs)) continue; if (!lhs || is_lhs_read_only(lhs)) continue;
// Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate); var lvalues = get_lvalues(candidate);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false; if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
var side_effects = value_has_side_effects(candidate); var side_effects = value_has_side_effects(candidate);
var hit = false, abort = false, replaced = false; var hit = candidate.name instanceof AST_SymbolFunarg;
var abort = false, replaced = false;
var tt = new TreeTransformer(function(node, descend) { var tt = new TreeTransformer(function(node, descend) {
if (abort) return node; if (abort) return node;
// Skip nodes before `candidate` as quickly as possible // Skip nodes before `candidate` as quickly as possible
@@ -803,6 +812,46 @@ merge(Compressor.prototype, {
} }
} }
function extract_args() {
var iife, fn = compressor.self();
if (fn instanceof AST_Function
&& !fn.name
&& !fn.uses_arguments
&& !fn.uses_eval
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn) {
var names = Object.create(null);
for (var i = fn.argnames.length; --i >= 0;) {
var sym = fn.argnames[i];
if (sym.name in names) continue;
names[sym.name] = true;
var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym);
else {
var tw = new TreeWalker(function(node) {
if (!arg) return true;
if (node instanceof AST_SymbolRef && fn.variables.has(node.name)) {
var s = node.definition().scope;
if (s !== scope) while (s = s.parent_scope) {
if (s === scope) return true;
}
arg = null;
}
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
arg = null;
return true;
}
});
arg.walk(tw);
}
if (arg) candidates.unshift(make_node(AST_VarDef, sym, {
name: sym,
value: arg
}));
}
}
}
function extract_candidates(expr) { function extract_candidates(expr) {
if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor) if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
|| expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) { || expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
@@ -823,7 +872,7 @@ merge(Compressor.prototype, {
function get_lhs(expr) { function get_lhs(expr) {
if (expr instanceof AST_VarDef) { if (expr instanceof AST_VarDef) {
var def = expr.name.definition(); var def = expr.name.definition();
if (def.orig.length > 1 if (def.orig.length > 1 && !(expr.name instanceof AST_SymbolFunarg)
|| def.references.length == 1 && !compressor.exposed(def)) { || def.references.length == 1 && !compressor.exposed(def)) {
return make_node(AST_SymbolRef, expr.name, expr.name); return make_node(AST_SymbolRef, expr.name, expr.name);
} }
@@ -865,6 +914,14 @@ merge(Compressor.prototype, {
} }
function remove_candidate(expr) { function remove_candidate(expr) {
if (expr.name instanceof AST_SymbolFunarg) {
var index = compressor.self().argnames.indexOf(expr.name);
var args = compressor.parent().args;
if (args[index]) args[index] = make_node(AST_Number, args[index], {
value: 0
});
return true;
}
var found = false; var found = false;
return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) { return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
if (found) return node; if (found) return node;
@@ -1153,21 +1210,21 @@ merge(Compressor.prototype, {
for (var i = 0, len = statements.length; i < len; i++) { for (var i = 0, len = statements.length; i < len; i++) {
var stat = statements[i]; var stat = statements[i];
if (prev) { if (prev) {
if (stat instanceof AST_For) { if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
try { var abort = false;
prev.body.walk(new TreeWalker(function(node){ prev.body.walk(new TreeWalker(function(node) {
if (node instanceof AST_Binary && node.operator == "in") if (abort || node instanceof AST_Scope) return true;
throw cons_seq; if (node instanceof AST_Binary && node.operator == "in") {
})); abort = true;
if (stat.init && !(stat.init instanceof AST_Definitions)) { return true;
stat.init = cons_seq(stat.init);
} }
else if (!stat.init) { }));
if (!abort) {
if (stat.init) stat.init = cons_seq(stat.init);
else {
stat.init = prev.body.drop_side_effect_free(compressor); stat.init = prev.body.drop_side_effect_free(compressor);
n--; n--;
} }
} catch(ex) {
if (ex !== cons_seq) throw ex;
} }
} }
else if (stat instanceof AST_If) { else if (stat instanceof AST_If) {
@@ -1487,13 +1544,8 @@ merge(Compressor.prototype, {
// descendant of AST_Node. // descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){ AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this; if (!compressor.option("evaluate")) return this;
try { var val = this._eval(compressor);
var val = this._eval(compressor); return !val || val instanceof RegExp || typeof val != "object" ? val : this;
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(ex) {
if (ex !== def) throw ex;
return this;
}
}); });
var unaryPrefix = makePredicate("! ~ - + void"); var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){ AST_Node.DEFMETHOD("is_constant", function(){
@@ -1539,27 +1591,28 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){ def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
}); });
def(AST_Lambda, function(){ def(AST_Lambda, return_this);
throw def;
});
function ev(node, compressor) { function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed"); if (!compressor) throw new Error("Compressor must be passed");
return node._eval(compressor); return node._eval(compressor);
}; };
def(AST_Node, function(){ def(AST_Node, return_this);
throw def; // not constant
});
def(AST_Constant, function(){ def(AST_Constant, function(){
return this.getValue(); return this.getValue();
}); });
def(AST_Array, function(compressor){ def(AST_Array, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
return this.elements.map(function(element) { var elements = [];
return ev(element, compressor); for (var i = 0, len = this.elements.length; i < len; i++) {
}); var element = this.elements[i];
var value = ev(element, compressor);
if (element === value) return this;
elements.push(value);
}
return elements;
} }
throw def; return this;
}); });
def(AST_Object, function(compressor){ def(AST_Object, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
@@ -1571,163 +1624,262 @@ merge(Compressor.prototype, {
key = key.name; key = key.name;
} else if (key instanceof AST_Node) { } else if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === prop.key) return this;
} }
if (typeof Object.prototype[key] === 'function') { if (typeof Object.prototype[key] === 'function') {
throw def; return this;
} }
val[key] = ev(prop.value, compressor); val[key] = ev(prop.value, compressor);
if (val[key] === prop.value) return this;
} }
return val; return val;
} }
throw def; return this;
}); });
def(AST_UnaryPrefix, function(compressor){ def(AST_UnaryPrefix, function(compressor){
var e = this.expression; // Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (this.operator == "typeof" && this.expression instanceof AST_Function) {
return typeof function(){};
}
var e = ev(this.expression, compressor);
if (e === this.expression) return this;
switch (this.operator) { switch (this.operator) {
case "!": return !ev(e, compressor); case "!": return !e;
case "typeof": case "typeof":
// Function would be evaluated to an array and so typeof would
// incorrectly return 'object'. Hence making is a special case.
if (e instanceof AST_Function) return typeof function(){};
e = ev(e, compressor);
// typeof <RegExp> returns "object" or "function" on different platforms // typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably // so cannot evaluate reliably
if (e instanceof RegExp) throw def; if (e instanceof RegExp) return this;
return typeof e; return typeof e;
case "void": return void ev(e, compressor); case "void": return void e;
case "~": return ~ev(e, compressor); case "~": return ~e;
case "-": return -ev(e, compressor); case "-": return -e;
case "+": return +ev(e, compressor); case "+": return +e;
} }
throw def; return this;
}); });
def(AST_Binary, function(c){ def(AST_Binary, function(compressor){
var left = this.left, right = this.right, result; var left = ev(this.left, compressor);
if (left === this.left) return this;
var right = ev(this.right, compressor);
if (right === this.right) return this;
var result;
switch (this.operator) { switch (this.operator) {
case "&&" : result = ev(left, c) && ev(right, c); break; case "&&" : result = left && right; break;
case "||" : result = ev(left, c) || ev(right, c); break; case "||" : result = left || right; break;
case "|" : result = ev(left, c) | ev(right, c); break; case "|" : result = left | right; break;
case "&" : result = ev(left, c) & ev(right, c); break; case "&" : result = left & right; break;
case "^" : result = ev(left, c) ^ ev(right, c); break; case "^" : result = left ^ right; break;
case "+" : result = ev(left, c) + ev(right, c); break; case "+" : result = left + right; break;
case "*" : result = ev(left, c) * ev(right, c); break; case "*" : result = left * right; break;
case "/" : result = ev(left, c) / ev(right, c); break; case "/" : result = left / right; break;
case "%" : result = ev(left, c) % ev(right, c); break; case "%" : result = left % right; break;
case "-" : result = ev(left, c) - ev(right, c); break; case "-" : result = left - right; break;
case "<<" : result = ev(left, c) << ev(right, c); break; case "<<" : result = left << right; break;
case ">>" : result = ev(left, c) >> ev(right, c); break; case ">>" : result = left >> right; break;
case ">>>" : result = ev(left, c) >>> ev(right, c); break; case ">>>" : result = left >>> right; break;
case "==" : result = ev(left, c) == ev(right, c); break; case "==" : result = left == right; break;
case "===" : result = ev(left, c) === ev(right, c); break; case "===" : result = left === right; break;
case "!=" : result = ev(left, c) != ev(right, c); break; case "!=" : result = left != right; break;
case "!==" : result = ev(left, c) !== ev(right, c); break; case "!==" : result = left !== right; break;
case "<" : result = ev(left, c) < ev(right, c); break; case "<" : result = left < right; break;
case "<=" : result = ev(left, c) <= ev(right, c); break; case "<=" : result = left <= right; break;
case ">" : result = ev(left, c) > ev(right, c); break; case ">" : result = left > right; break;
case ">=" : result = ev(left, c) >= ev(right, c); break; case ">=" : result = left >= right; break;
default: default:
throw def; return this;
} }
if (isNaN(result) && c.find_parent(AST_With)) { if (isNaN(result) && compressor.find_parent(AST_With)) {
// leave original expression as is // leave original expression as is
throw def; return this;
} }
return result; return result;
}); });
def(AST_Conditional, function(compressor){ def(AST_Conditional, function(compressor){
return ev(this.condition, compressor) var condition = ev(this.condition, compressor);
? ev(this.consequent, compressor) if (condition === this.condition) return this;
: ev(this.alternative, compressor); var node = condition ? this.consequent : this.alternative;
var value = ev(node, compressor);
return value === node ? this : value;
}); });
def(AST_SymbolRef, function(compressor){ def(AST_SymbolRef, function(compressor){
if (!compressor.option("reduce_vars") || this._evaluating) throw def; if (!compressor.option("reduce_vars")) return this;
this._evaluating = true; var fixed = this.fixed_value();
try { if (!fixed) return this;
var fixed = this.fixed_value(); this._eval = return_this;
if (!fixed) throw def; var value = ev(fixed, compressor);
var value = ev(fixed, compressor); if (value === fixed) {
if (!HOP(fixed, "_eval")) fixed._eval = function() { delete this._eval;
return value; return this;
};
if (value && typeof value == "object" && this.definition().escaped) throw def;
return value;
} finally {
this._evaluating = false;
} }
if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
};
if (value && typeof value == "object" && this.definition().escaped) {
delete this._eval;
return this;
}
this._eval = fixed._eval;
return value;
}); });
var global_objs = {
Array: Array,
Boolean: Boolean,
Math: Math,
Number: Number,
RegExp: RegExp,
Object: Object,
String: String,
};
function convert_to_predicate(obj) {
for (var key in obj) {
obj[key] = makePredicate(obj[key]);
}
}
var static_values = {
Math: [
"E",
"LN10",
"LN2",
"LOG2E",
"LOG10E",
"PI",
"SQRT1_2",
"SQRT2",
],
Number: [
"MAX_VALUE",
"MIN_VALUE",
"NaN",
"NEGATIVE_INFINITY",
"POSITIVE_INFINITY",
],
};
convert_to_predicate(static_values);
def(AST_PropAccess, function(compressor){ def(AST_PropAccess, function(compressor){
if (compressor.option("unsafe")) { if (compressor.option("unsafe")) {
var key = this.property; var key = this.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === this.property) return this;
} }
var val = ev(this.expression, compressor); var exp = this.expression;
if (val && HOP(val, key)) { var val;
return val[key]; if (exp instanceof AST_SymbolRef && exp.undeclared()) {
if (!(static_values[exp.name] || return_false)(key)) return this;
val = global_objs[exp.name];
} else {
val = ev(exp, compressor);
if (!val || val === exp || !HOP(val, key)) return this;
} }
return val[key];
} }
throw def; return this;
}); });
var object_fns = [ var object_fns = [
'constructor', "constructor",
'toString', "toString",
'valueOf', "valueOf",
]; ];
var native_fns = { var native_fns = {
Array: makePredicate([ Array: [
'indexOf', "indexOf",
'join', "join",
'lastIndexOf', "lastIndexOf",
'slice', "slice",
].concat(object_fns)), ].concat(object_fns),
Boolean: makePredicate(object_fns), Boolean: object_fns,
Number: makePredicate([ Number: [
'toExponential', "toExponential",
'toFixed', "toFixed",
'toPrecision', "toPrecision",
].concat(object_fns)), ].concat(object_fns),
RegExp: makePredicate([ RegExp: [
'test', "test",
].concat(object_fns)), ].concat(object_fns),
String: makePredicate([ String: [
'charAt', "charAt",
'charCodeAt', "charCodeAt",
'concat', "concat",
'indexOf', "indexOf",
'italics', "italics",
'lastIndexOf', "lastIndexOf",
'match', "match",
'replace', "replace",
'search', "search",
'slice', "slice",
'split', "split",
'substr', "substr",
'substring', "substring",
'trim', "trim",
].concat(object_fns)), ].concat(object_fns),
}; };
convert_to_predicate(native_fns);
var static_fns = {
Array: [
"isArray",
],
Math: [
"abs",
"acos",
"asin",
"atan",
"ceil",
"cos",
"exp",
"floor",
"log",
"round",
"sin",
"sqrt",
"tan",
"atan2",
"pow",
"max",
"min"
],
Number: [
"isFinite",
"isNaN",
],
Object: [
"keys",
"getOwnPropertyNames",
],
String: [
"fromCharCode",
],
};
convert_to_predicate(static_fns);
def(AST_Call, function(compressor){ def(AST_Call, function(compressor){
var exp = this.expression; var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) { if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property; var key = exp.property;
if (key instanceof AST_Node) { if (key instanceof AST_Node) {
key = ev(key, compressor); key = ev(key, compressor);
if (key === exp.property) return this;
} }
var val = ev(exp.expression, compressor); var val;
if ((val && native_fns[val.constructor.name] || return_false)(key)) { var e = exp.expression;
return val[key].apply(val, this.args.map(function(arg) { if (e instanceof AST_SymbolRef && e.undeclared()) {
return ev(arg, compressor); if (!(static_fns[e.name] || return_false)(key)) return this;
})); val = global_objs[e.name];
} else {
val = ev(e, compressor);
if (val === e || !(val && native_fns[val.constructor.name] || return_false)(key)) return this;
} }
var args = [];
for (var i = 0, len = this.args.length; i < len; i++) {
var arg = this.args[i];
var value = ev(arg, compressor);
if (arg === value) return this;
args.push(value);
}
return val[key].apply(val, args);
} }
throw def; return this;
});
def(AST_New, function(compressor){
throw def;
}); });
def(AST_New, return_this);
})(function(node, func){ })(function(node, func){
node.DEFMETHOD("_eval", func); node.DEFMETHOD("_eval", func);
}); });
@@ -3168,80 +3320,53 @@ merge(Compressor.prototype, {
var value = stat.value; var value = stat.value;
if (!value || value.is_constant_expression()) { if (!value || value.is_constant_expression()) {
var args = self.args.concat(value || make_node(AST_Undefined, self)); var args = self.args.concat(value || make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).optimize(compressor);
} }
} }
if (exp instanceof AST_Function) { if (exp instanceof AST_Function) {
if (compressor.option("inline") if (compressor.option("inline")
&& !exp.name && !exp.name
&& exp.body.length == 1
&& !exp.uses_arguments && !exp.uses_arguments
&& !exp.uses_eval && !exp.uses_eval
&& exp.body.length == 1
&& all(exp.argnames, function(arg) {
return arg.__unused;
})
&& !self.has_pure_annotation(compressor)) { && !self.has_pure_annotation(compressor)) {
var value; var value;
if (stat instanceof AST_Return) { if (stat instanceof AST_Return) {
value = stat.value.clone(true); value = stat.value;
} else if (stat instanceof AST_SimpleStatement) { } else if (stat instanceof AST_SimpleStatement) {
value = make_node(AST_UnaryPrefix, stat, { value = make_node(AST_UnaryPrefix, stat, {
operator: "void", operator: "void",
expression: stat.body.clone(true) expression: stat.body
}); });
} }
if (value) { if (value) {
var fn = exp.clone(); var tw = new TreeWalker(function(node) {
fn.argnames = []; if (!value) return true;
fn.body = []; if (node instanceof AST_SymbolRef) {
if (exp.argnames.length > 0) { var ref = node.scope.find_variable(node);
fn.body.push(make_node(AST_Var, self, { if (ref && ref.scope.parent_scope === fn.parent_scope) {
definitions: exp.argnames.map(function(sym, i) { value = null;
var arg = self.args[i];
return make_node(AST_VarDef, sym, {
name: sym,
value: arg ? arg.clone(true) : make_node(AST_Undefined, self)
});
})
}));
}
if (self.args.length > exp.argnames.length) {
fn.body.push(make_node(AST_SimpleStatement, self, {
body: make_sequence(self, self.args.slice(exp.argnames.length).map(function(node) {
return node.clone(true);
}))
}));
}
fn.body.push(make_node(AST_Return, self, {
value: value
}));
var body = fn.transform(compressor).body;
if (body.length == 0) return make_node(AST_Undefined, self);
if (body.length == 1 && body[0] instanceof AST_Return) {
value = body[0].value;
if (!value) return make_node(AST_Undefined, self);
var tw = new TreeWalker(function(node) {
if (value === self) return true;
if (node instanceof AST_SymbolRef) {
var ref = node.scope.find_variable(node);
if (ref && ref.scope.parent_scope === fn.parent_scope) {
value = self;
return true;
}
}
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
value = self;
return true; return true;
} }
}); }
value.walk(tw); if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
if (value !== self) value = best_of(compressor, value, self); value = null;
} else { return true;
value = self; }
} });
if (value !== self) return value; value.walk(tw);
}
if (value) {
var args = self.args.concat(value);
return make_sequence(self, args).optimize(compressor);
} }
} }
if (compressor.option("side_effects") && all(exp.body, is_empty)) { if (compressor.option("side_effects") && all(exp.body, is_empty)) {
var args = self.args.concat(make_node(AST_Undefined, self)); var args = self.args.concat(make_node(AST_Undefined, self));
return make_sequence(self, args).transform(compressor); return make_sequence(self, args).optimize(compressor);
} }
} }
if (compressor.option("drop_console")) { if (compressor.option("drop_console")) {
@@ -3559,7 +3684,8 @@ merge(Compressor.prototype, {
case "==": case "==":
case "!=": case "!=":
// "undefined" == typeof x => undefined === x // "undefined" == typeof x => undefined === x
if (self.left instanceof AST_String if (compressor.option("typeofs")
&& self.left instanceof AST_String
&& self.left.value == "undefined" && self.left.value == "undefined"
&& self.right instanceof AST_UnaryPrefix && self.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") { && self.right.operator == "typeof") {
@@ -4266,6 +4392,17 @@ merge(Compressor.prototype, {
return self; return self;
}); });
AST_Lambda.DEFMETHOD("contains_this", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {
if (result) return true;
if (node instanceof AST_This) return result = true;
if (node !== self && node instanceof AST_Scope) return true;
}));
return result;
});
OPT(AST_Dot, function(self, compressor){ OPT(AST_Dot, function(self, compressor){
var def = self.resolve_defines(compressor); var def = self.resolve_defines(compressor);
if (def) { if (def) {
@@ -4280,6 +4417,20 @@ merge(Compressor.prototype, {
}) })
}).optimize(compressor); }).optimize(compressor);
} }
if (compressor.option("unsafe") && self.expression instanceof AST_Object) {
var values = self.expression.properties;
for (var i = values.length; --i >= 0;) {
if (values[i].key === prop) {
var value = values[i].value;
if (value instanceof AST_Function ? !value.contains_this() : !value.has_side_effects(compressor)) {
var obj = self.expression.clone();
obj.properties = obj.properties.slice();
obj.properties.splice(i, 1);
return make_sequence(self, [ obj, value ]).optimize(compressor);
}
}
}
}
if (compressor.option("unsafe_proto") if (compressor.option("unsafe_proto")
&& self.expression instanceof AST_Dot && self.expression instanceof AST_Dot
&& self.expression.property == "prototype") { && self.expression.property == "prototype") {

View File

@@ -27,6 +27,23 @@ function set_shorthand(name, options, keys) {
} }
} }
function init_cache(cache) {
if (!cache) return;
if (!("cname" in cache)) cache.cname = -1;
if (!("props" in cache)) {
cache.props = new Dictionary();
} else if (!(cache.props instanceof Dictionary)) {
cache.props = Dictionary.fromObject(cache.props);
}
}
function to_json(cache) {
return {
cname: cache.cname,
props: cache.props.toObject()
};
}
function minify(files, options) { function minify(files, options) {
var warn_function = AST_Node.warn_function; var warn_function = AST_Node.warn_function;
try { try {
@@ -35,6 +52,7 @@ function minify(files, options) {
ie8: false, ie8: false,
keep_fnames: false, keep_fnames: false,
mangle: {}, mangle: {},
nameCache: null,
output: {}, output: {},
parse: {}, parse: {},
sourceMap: false, sourceMap: false,
@@ -52,7 +70,7 @@ function minify(files, options) {
set_shorthand("warnings", options, [ "compress" ]); set_shorthand("warnings", options, [ "compress" ]);
if (options.mangle) { if (options.mangle) {
options.mangle = defaults(options.mangle, { options.mangle = defaults(options.mangle, {
cache: null, cache: options.nameCache && (options.nameCache.vars || {}),
eval: false, eval: false,
ie8: false, ie8: false,
keep_fnames: false, keep_fnames: false,
@@ -60,6 +78,16 @@ function minify(files, options) {
reserved: [], reserved: [],
toplevel: false, toplevel: false,
}, true); }, true);
if (options.nameCache && options.mangle.properties) {
if (typeof options.mangle.properties != "object") {
options.mangle.properties = {};
}
if (!("cache" in options.mangle.properties)) {
options.mangle.properties.cache = options.nameCache.props || {};
}
}
init_cache(options.mangle.cache);
init_cache(options.mangle.properties.cache);
} }
if (options.sourceMap) { if (options.sourceMap) {
options.sourceMap = defaults(options.sourceMap, { options.sourceMap = defaults(options.sourceMap, {
@@ -153,6 +181,12 @@ function minify(files, options) {
} }
} }
} }
if (options.nameCache && options.mangle) {
if (options.mangle.cache) options.nameCache.vars = to_json(options.mangle.cache);
if (options.mangle.properties && options.mangle.properties.cache) {
options.nameCache.props = to_json(options.mangle.properties.cache);
}
}
if (timings) { if (timings) {
timings.end = Date.now(); timings.end = Date.now();
result.timings = { result.timings = {

View File

@@ -672,14 +672,15 @@ function OutputStream(options) {
// parens around it too, otherwise the call will be // parens around it too, otherwise the call will be
// interpreted as passing the arguments to the upper New // interpreted as passing the arguments to the upper New
// expression. // expression.
try { var parens = false;
this.walk(new TreeWalker(function(node){ this.walk(new TreeWalker(function(node) {
if (node instanceof AST_Call) throw p; if (parens || node instanceof AST_Scope) return true;
})); if (node instanceof AST_Call) {
} catch(ex) { parens = true;
if (ex !== p) throw ex; return true;
return true; }
} }));
return parens;
} }
}); });
@@ -1073,19 +1074,17 @@ function OutputStream(options) {
}); });
function parenthesize_for_noin(node, output, noin) { function parenthesize_for_noin(node, output, noin) {
if (!noin) node.print(output); var parens = false;
else try { // need to take some precautions here:
// need to take some precautions here: // https://github.com/mishoo/UglifyJS2/issues/60
// https://github.com/mishoo/UglifyJS2/issues/60 if (noin) node.walk(new TreeWalker(function(node) {
node.walk(new TreeWalker(function(node){ if (parens || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") if (node instanceof AST_Binary && node.operator == "in") {
throw output; parens = true;
})); return true;
node.print(output); }
} catch(ex) { }));
if (ex !== output) throw ex; node.print(output, parens);
node.print(output, true);
}
}; };
DEFPRINT(AST_VarDef, function(self, output){ DEFPRINT(AST_VarDef, function(self, output){

View File

@@ -70,7 +70,7 @@ TreeTransformer.prototype = new TreeWalker;
if (y !== undefined) x = y; if (y !== undefined) x = y;
} }
} }
tw.pop(this); tw.pop();
return x; return x;
}); });
}; };

View File

@@ -4,7 +4,7 @@
"homepage": "http://lisperator.net/uglifyjs", "homepage": "http://lisperator.net/uglifyjs",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)", "author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"version": "3.0.21", "version": "3.0.24",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=0.8.0"
}, },

View File

@@ -1978,10 +1978,10 @@ chained_3: {
} }
expect: { expect: {
console.log(function(a, b) { console.log(function(a, b) {
var c = a, c = b; var c = 1, c = b;
b++; b++;
return c; return c;
}(1, 2)); }(0, 2));
} }
expect_stdout: "2" expect_stdout: "2"
} }
@@ -2186,3 +2186,159 @@ compound_assignment: {
} }
expect_stdout: "4" expect_stdout: "4"
} }
issue_2187_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var a = 1;
!function(foo) {
foo();
var a = 2;
console.log(a);
}(function() {
console.log(a);
});
}
expect: {
var a = 1;
!function(foo) {
foo();
var a = 2;
console.log(a);
}(function() {
console.log(a);
});
}
expect_stdout: [
"1",
"2",
]
}
issue_2187_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
var b = 1;
console.log(function(a) {
return a && ++b;
}(b--));
}
expect: {
var b = 1;
console.log(function(a) {
return b-- && ++b;
}());
}
expect_stdout: "1"
}
issue_2187_3: {
options = {
collapse_vars: true,
inline: true,
unused: true,
}
input: {
var b = 1;
console.log(function(a) {
return a && ++b;
}(b--));
}
expect: {
var b = 1;
console.log(b-- && ++b);
}
expect_stdout: "1"
}
issue_2203_1: {
options = {
collapse_vars: true,
unused: true,
}
input: {
a = "FAIL";
console.log({
a: "PASS",
b: function() {
return function(c) {
return c.a;
}((String, (Object, this)));
}
}.b());
}
expect: {
a = "FAIL";
console.log({
a: "PASS",
b: function() {
return function(c) {
return c.a;
}((String, (Object, this)));
}
}.b());
}
expect_stdout: "PASS"
}
issue_2203_2: {
options = {
collapse_vars: true,
unused: true,
}
input: {
a = "PASS";
console.log({
a: "FAIL",
b: function() {
return function(c) {
return c.a;
}((String, (Object, function() {
return this;
}())));
}
}.b());
}
expect: {
a = "PASS";
console.log({
a: "FAIL",
b: function() {
return function(c) {
return (String, (Object, function() {
return this;
}())).a;
}();
}
}.b());
}
expect_stdout: "PASS"
}
duplicate_argname: {
options = {
collapse_vars: true,
unused: true,
}
input: {
function f() { return "PASS"; }
console.log(function(a, a) {
f++;
return a;
}("FAIL", f()));
}
expect: {
function f() { return "PASS"; }
console.log(function(a, a) {
f++;
return a;
}("FAIL", f()));
}
expect_stdout: "PASS"
}

View File

@@ -1113,6 +1113,7 @@ issue_2105: {
options = { options = {
collapse_vars: true, collapse_vars: true,
inline: true, inline: true,
passes: 3,
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
unused: true, unused: true,
@@ -1138,7 +1139,7 @@ issue_2105: {
}); });
} }
expect: { expect: {
!void function() { (function() {
var quux = function() { var quux = function() {
console.log("PASS"); console.log("PASS");
}; };
@@ -1148,7 +1149,7 @@ issue_2105: {
quux(); quux();
} }
}; };
}().prop(); })().prop();
} }
expect_stdout: "PASS" expect_stdout: "PASS"
} }

View File

@@ -250,22 +250,26 @@ unsafe_constant: {
unsafe_object: { unsafe_object: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: 1 };
console.log( console.log(
({a:1}) + 1, o + 1,
({a:1}).a + 1, o.a + 1,
({a:1}).b + 1, o.b + 1,
({a:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: 1 };
console.log( console.log(
({a:1}) + 1, o + 1,
2, 2,
({a:1}).b + 1, o.b + 1,
1..b + 1 1..b + 1
); );
} }
@@ -274,22 +278,26 @@ unsafe_object: {
unsafe_object_nested: { unsafe_object_nested: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 } };
console.log( console.log(
({a:{b:1}}) + 1, o + 1,
({a:{b:1}}).a + 1, o.a + 1,
({a:{b:1}}).b + 1, o.b + 1,
({a:{b:1}}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 } };
console.log( console.log(
({a:{b:1}}) + 1, o + 1,
({a:{b:1}}).a + 1, o.a + 1,
({a:{b:1}}).b + 1, o.b + 1,
2 2
); );
} }
@@ -298,21 +306,25 @@ unsafe_object_nested: {
unsafe_object_complex: { unsafe_object_complex: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 }, b: 1 };
console.log( console.log(
({a:{b:1},b:1}) + 1, o + 1,
({a:{b:1},b:1}).a + 1, o.a + 1,
({a:{b:1},b:1}).b + 1, o.b + 1,
({a:{b:1},b:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 }, b: 1 };
console.log( console.log(
({a:{b:1},b:1}) + 1, o + 1,
({a:{b:1},b:1}).a + 1, o.a + 1,
2, 2,
2 2
); );
@@ -322,22 +334,26 @@ unsafe_object_complex: {
unsafe_object_repeated: { unsafe_object_repeated: {
options = { options = {
evaluate : true, evaluate: true,
unsafe : true reduce_vars: true,
toplevel: true,
unsafe: true,
} }
input: { input: {
var o = { a: { b: 1 }, a: 1 };
console.log( console.log(
({a:{b:1},a:1}) + 1, o + 1,
({a:{b:1},a:1}).a + 1, o.a + 1,
({a:{b:1},a:1}).b + 1, o.b + 1,
({a:{b:1},a:1}).a.b + 1 o.a.b + 1
); );
} }
expect: { expect: {
var o = { a: { b: 1 }, a: 1 };
console.log( console.log(
({a:{b:1},a:1}) + 1, o + 1,
2, 2,
({a:{b:1},a:1}).b + 1, o.b + 1,
1..b + 1 1..b + 1
); );
} }
@@ -386,9 +402,9 @@ unsafe_function: {
expect: { expect: {
console.log( console.log(
({a:{b:1},b:function(){}}) + 1, ({a:{b:1},b:function(){}}) + 1,
({a:{b:1},b:function(){}}).a + 1, ({b:function(){}}, {b:1}) + 1,
({a:{b:1},b:function(){}}).b + 1, ({a:{b:1}}, function(){}) + 1,
({a:{b:1},b:function(){}}).a.b + 1 ({b:function(){}}, {b:1}).b + 1
); );
} }
expect_stdout: true expect_stdout: true
@@ -636,8 +652,8 @@ unsafe_prototype_function: {
var d = ({toString: 0}) + ""; var d = ({toString: 0}) + "";
var e = (({valueOf: 0}) + "")[2]; var e = (({valueOf: 0}) + "")[2];
var f = (({toString: 0}) + "")[2]; var f = (({toString: 0}) + "")[2];
var g = ({valueOf: 0}).valueOf(); var g = ({}, 0)();
var h = "" + ({toString: 0}); var h = ({}, 0)();
} }
} }
@@ -1069,3 +1085,75 @@ string_charCodeAt: {
} }
expect_stdout: "NaN" expect_stdout: "NaN"
} }
issue_2207_1: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(String.fromCharCode(65));
console.log(Math.max(3, 6, 2, 7, 3, 4));
console.log(Math.cos(1.2345));
console.log(Math.cos(1.2345) - Math.sin(4.321));
console.log(Math.pow(Math.PI, Math.E - Math.LN10));
}
expect: {
console.log("A");
console.log(7);
console.log(Math.cos(1.2345));
console.log(1.2543732512566947);
console.log(1.6093984514472044);
}
expect_stdout: true
}
issue_2207_2: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Math.E);
console.log(Math.LN10);
console.log(Math.LN2);
console.log(Math.LOG2E);
console.log(Math.LOG10E);
console.log(Math.PI);
console.log(Math.SQRT1_2);
console.log(Math.SQRT2);
}
expect: {
console.log(Math.E);
console.log(Math.LN10);
console.log(Math.LN2);
console.log(Math.LOG2E);
console.log(Math.LOG10E);
console.log(Math.PI);
console.log(Math.SQRT1_2);
console.log(Math.SQRT2);
}
expect_stdout: true
}
issue_2207_3: {
options = {
evaluate: true,
unsafe: true,
}
input: {
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
console.log(Number.NaN);
console.log(Number.NEGATIVE_INFINITY);
console.log(Number.POSITIVE_INFINITY);
}
expect: {
console.log(Number.MAX_VALUE);
console.log(5e-324);
console.log(NaN);
console.log(-1/0);
console.log(1/0);
}
expect_stdout: true
}

View File

@@ -468,11 +468,9 @@ issue_2114_1: {
} }
expect: { expect: {
var c = 0; var c = 0;
!function() { c = 1 + (c += 1), function() {
0;
}((c += 1, c = 1 + c, function() {
var b = void (b && (b.b += (c += 1, 0))); var b = void (b && (b.b += (c += 1, 0)));
}())); }();
console.log(c); console.log(c);
} }
expect_stdout: "2" expect_stdout: "2"

View File

@@ -37,6 +37,7 @@ object: {
VALUE: 42, VALUE: 42,
}, },
}, },
side_effects: true,
unsafe: true, unsafe: true,
} }
input: { input: {
@@ -140,9 +141,9 @@ mixed: {
console.log(CONFIG); console.log(CONFIG);
} }
expect_warnings: [ expect_warnings: [
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:126,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:127,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:129,8]', 'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:128,22]',
'WARN: global_defs CONFIG.VALUE redefined [test/compress/global_defs.js:130,8]',
] ]
} }

View File

@@ -1,6 +1,7 @@
typeof_eq_undefined: { typeof_eq_undefined: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
input: { input: {
var a = typeof b != "undefined"; var a = typeof b != "undefined";
@@ -24,6 +25,7 @@ typeof_eq_undefined_ie8: {
options = { options = {
comparisons: true, comparisons: true,
ie8: true, ie8: true,
typeofs: true,
} }
input: { input: {
var a = typeof b != "undefined"; var a = typeof b != "undefined";
@@ -45,7 +47,8 @@ typeof_eq_undefined_ie8: {
undefined_redefined: { undefined_redefined: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
input: { input: {
function f(undefined) { function f(undefined) {
@@ -58,7 +61,8 @@ undefined_redefined: {
undefined_redefined_mangle: { undefined_redefined_mangle: {
options = { options = {
comparisons: true comparisons: true,
typeofs: true,
} }
mangle = {} mangle = {}
input: { input: {

View File

@@ -419,7 +419,7 @@ wrap_iife_in_return_call: {
expect_exact: '(void console.log("test"))();' expect_exact: '(void console.log("test"))();'
} }
pure_annotation: { pure_annotation_1: {
options = { options = {
inline: true, inline: true,
side_effects: true, side_effects: true,
@@ -432,6 +432,20 @@ pure_annotation: {
expect_exact: "" expect_exact: ""
} }
pure_annotation_2: {
options = {
collapse_vars: true,
inline: true,
side_effects: true,
}
input: {
/*@__PURE__*/(function(n) {
console.log("hello", n);
}(42));
}
expect_exact: ""
}
drop_fargs: { drop_fargs: {
options = { options = {
cascade: true, cascade: true,
@@ -449,9 +463,7 @@ drop_fargs: {
} }
expect: { expect: {
var a = 1; var a = 1;
!function() { ++a && a.var, a++;
a++;
}(++a && a.var);
console.log(a); console.log(a);
} }
expect_stdout: "3" expect_stdout: "3"
@@ -474,9 +486,7 @@ keep_fargs: {
} }
expect: { expect: {
var a = 1; var a = 1;
!function(a_1) { ++a && a.var, a++;
a++;
}(++a && a.var);
console.log(a); console.log(a);
} }
expect_stdout: "3" expect_stdout: "3"

View File

@@ -436,3 +436,17 @@ do_switch: {
} while (false); } while (false);
} }
} }
in_parenthesis_1: {
input: {
for (("foo" in {});0;);
}
expect_exact: 'for(("foo"in{});0;);'
}
in_parenthesis_2: {
input: {
for ((function(){ "foo" in {}; });0;);
}
expect_exact: 'for(function(){"foo"in{}};0;);'
}

View File

@@ -82,3 +82,19 @@ new_with_unary_prefix: {
} }
expect_exact: 'var bar=(+new Date).toString(32);'; expect_exact: 'var bar=(+new Date).toString(32);';
} }
dot_parenthesis_1: {
input: {
console.log(new (Math.random().constructor) instanceof Number);
}
expect_exact: "console.log(new(Math.random().constructor)instanceof Number);"
expect_stdout: "true"
}
dot_parenthesis_2: {
input: {
console.log(typeof new function(){Math.random()}.constructor);
}
expect_exact: "console.log(typeof new function(){Math.random()}.constructor);"
expect_stdout: "function"
}

View File

@@ -657,3 +657,116 @@ accessor_this: {
expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);' expect_exact: 'var a=1;var b={get this(){return a},set this(c){a=c}};console.log(b.this,b.this=2,b.this);'
expect_stdout: "1 2 2" expect_stdout: "1 2 2"
} }
issue_2208_1: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
p: function() {
return 42;
}
}.p());
}
expect: {
console.log(42);
}
expect_stdout: "42"
}
issue_2208_2: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
a: 42,
p: function() {
return this.a;
}
}.p());
}
expect: {
console.log({
a: 42,
p: function() {
return this.a;
}
}.p());
}
expect_stdout: "42"
}
issue_2208_3: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
a = 42;
console.log({
p: function() {
return function() {
return this.a;
}();
}
}.p());
}
expect: {
a = 42;
console.log(function() {
return this.a;
}());
}
expect_stdout: "42"
}
issue_2208_4: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
function foo() {}
console.log({
a: foo(),
p: function() {
return 42;
}
}.p());
}
expect: {
function foo() {}
console.log((foo(), function() {
return 42;
})());
}
expect_stdout: "42"
}
issue_2208_5: {
options = {
inline: true,
side_effects: true,
unsafe: true,
}
input: {
console.log({
p: "FAIL",
p: function() {
return 42;
}
}.p());
}
expect: {
console.log(42);
}
expect_stdout: "42"
}

View File

@@ -1469,6 +1469,7 @@ issue_1670_1: {
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
switches: true, switches: true,
typeofs: true,
unused: true, unused: true,
} }
input: { input: {
@@ -1532,6 +1533,7 @@ issue_1670_3: {
reduce_vars: true, reduce_vars: true,
side_effects: true, side_effects: true,
switches: true, switches: true,
typeofs: true,
unused: true, unused: true,
} }
input: { input: {

View File

@@ -176,6 +176,11 @@ for_sequences: {
// 4 // 4
x = (foo in bar); x = (foo in bar);
for (y = 5; false;); for (y = 5; false;);
// 5
x = function() {
foo in bar;
};
for (y = 5; false;);
} }
expect: { expect: {
// 1 // 1
@@ -188,6 +193,10 @@ for_sequences: {
// 4 // 4
x = (foo in bar); x = (foo in bar);
for (y = 5; false;); for (y = 5; false;);
// 5
for (x = function() {
foo in bar;
}, y = 5; false;);
} }
} }

View File

@@ -26,12 +26,12 @@ describe("bin/uglifyjs with input file globs", function() {
}); });
}); });
it("bin/uglifyjs with multiple input file globs.", function(done) { it("bin/uglifyjs with multiple input file globs.", function(done) {
var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel'; var command = uglifyjscmd + ' "test/input/issue-1242/???.es5" "test/input/issue-1242/*.js" -mc toplevel,passes=2';
exec(command, function(err, stdout) { exec(command, function(err, stdout) {
if (err) throw err; if (err) throw err;
assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n'); assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",22);\n');
done(); done();
}); });
}); });

View File

@@ -1,6 +1,7 @@
var Uglify = require('../../'); var Uglify = require('../../');
var assert = require("assert"); var assert = require("assert");
var readFileSync = require("fs").readFileSync; var readFileSync = require("fs").readFileSync;
var run_code = require("../sandbox").run_code;
function read(path) { function read(path) {
return readFileSync(path, "utf8"); return readFileSync(path, "utf8");
@@ -20,6 +21,58 @@ describe("minify", function() {
assert.strictEqual(result.code, "alert(2);"); assert.strictEqual(result.code, "alert(2);");
}); });
it("Should work with mangle.cache", function() {
var cache = {};
var original = "";
var compressed = "";
[
"bar.es5",
"baz.es5",
"foo.es5",
"qux.js",
].forEach(function(file) {
var code = read("test/input/issue-1242/" + file);
var result = Uglify.minify(code, {
mangle: {
cache: cache,
toplevel: true
}
});
if (result.error) throw result.error;
original += code;
compressed += result.code;
});
assert.strictEqual(JSON.stringify(cache).slice(0, 20), '{"cname":5,"props":{');
assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);');
assert.strictEqual(run_code(compressed), run_code(original));
});
it("Should work with nameCache", function() {
var cache = {};
var original = "";
var compressed = "";
[
"bar.es5",
"baz.es5",
"foo.es5",
"qux.js",
].forEach(function(file) {
var code = read("test/input/issue-1242/" + file);
var result = Uglify.minify(code, {
mangle: {
toplevel: true
},
nameCache: cache
});
if (result.error) throw result.error;
original += code;
compressed += result.code;
});
assert.strictEqual(JSON.stringify(cache).slice(0, 28), '{"vars":{"cname":5,"props":{');
assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);');
assert.strictEqual(run_code(compressed), run_code(original));
});
describe("keep_quoted_props", function() { describe("keep_quoted_props", function() {
it("Should preserve quotes in object literals", function() { it("Should preserve quotes in object literals", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';