Compare commits

...

19 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
Alex Lam S.L
5e6f26445f v3.0.21 2017-06-29 00:49:06 +08:00
Alex Lam S.L
f0a99125ee improve unsafe_Func (#2171)
- minimise disturbance to `compute_char_frequency()`
- remove extraneous quotation marks
2017-06-27 23:53:42 +08:00
Alex Lam S.L
1e4de2e6d3 parse @global_defs as expressions (#2169)
- let parser rejects non-conformant input
- eliminate need for extraneous parenthesis
2017-06-27 10:31:19 +08:00
22 changed files with 1044 additions and 324 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.
--keep-fnames Do not mangle/drop function names. Useful for
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)
--source-map [options] Enable source map/specify source map options:
`base` Path to compute relative paths from input files.
@@ -383,7 +383,47 @@ var code = {
var options = { toplevel: true };
var result = UglifyJS.minify(code, options);
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:
@@ -461,6 +501,13 @@ if (result.error) throw result.error;
- `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.
- `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.
## Minify options structure
@@ -487,6 +534,7 @@ if (result.error) throw result.error;
sourceMap: {
// source map options
},
nameCache: null, // or specify a name cache object
toplevel: 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
? 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
statically determine the condition
@@ -825,7 +877,6 @@ when this flag is on:
- `new Object()` → `{}`
- `String(exp)` or `exp.toString()` → `"" + exp`
- `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
scope; we do it because the variable name will be mangled, typically
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
[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 = {};
options.mangle.properties = program.mangleProps;
}
var cache;
if (program.nameCache) {
cache = 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");
}
}
options.nameCache = JSON.parse(read_file(program.nameCache, "{}"));
}
if (program.output == "ast") {
options.output = {
@@ -266,9 +257,7 @@ function run() {
print(result.code);
}
if (program.nameCache) {
fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) {
return value instanceof UglifyJS.Dictionary ? value.toObject() : value;
}));
fs.writeFileSync(program.nameCache, JSON.stringify(options.nameCache));
}
if (result.timings) for (var phase in result.timings) {
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) {
return skip_keys.indexOf(key) >= 0;
}

View File

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

View File

@@ -80,6 +80,7 @@ function Compressor(options, false_by_default) {
switches : !false_by_default,
top_retain : null,
toplevel : !!(options && options["top_retain"]),
typeofs : !false_by_default,
unsafe : false,
unsafe_comps : false,
unsafe_Func : false,
@@ -92,12 +93,9 @@ function Compressor(options, false_by_default) {
var global_defs = this.options["global_defs"];
if (typeof global_defs == "object") for (var key in global_defs) {
if (/^@/.test(key) && HOP(global_defs, key)) {
var ast = parse(global_defs[key]);
if (ast.body.length == 1 && ast.body[0] instanceof AST_SimpleStatement) {
global_defs[key.slice(1)] = ast.body[0].body;
} else throw new Error(string_template("Can't handle expression: {value}", {
value: global_defs[key]
}));
global_defs[key.slice(1)] = parse(global_defs[key], {
expression: true
});
}
}
var pure_funcs = this.options["pure_funcs"];
@@ -717,15 +715,23 @@ merge(Compressor.prototype, {
var candidates = [];
var stat_index = statements.length;
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]);
while (candidates.length > 0) {
var candidate = candidates.pop();
var lhs = get_lhs(candidate);
if (!lhs || is_lhs_read_only(lhs)) continue;
// Locate symbols which may execute code outside of scanning range
var lvalues = get_lvalues(candidate);
if (lhs instanceof AST_SymbolRef) lvalues[lhs.name] = false;
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) {
if (abort) return node;
// Skip nodes before `candidate` as quickly as possible
@@ -806,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) {
if (expr instanceof AST_Assign && !expr.left.has_side_effects(compressor)
|| expr instanceof AST_Unary && (expr.operator == "++" || expr.operator == "--")) {
@@ -826,7 +872,7 @@ merge(Compressor.prototype, {
function get_lhs(expr) {
if (expr instanceof AST_VarDef) {
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)) {
return make_node(AST_SymbolRef, expr.name, expr.name);
}
@@ -868,6 +914,14 @@ merge(Compressor.prototype, {
}
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;
return statements[stat_index].transform(new TreeTransformer(function(node, descend, in_list) {
if (found) return node;
@@ -1156,21 +1210,21 @@ merge(Compressor.prototype, {
for (var i = 0, len = statements.length; i < len; i++) {
var stat = statements[i];
if (prev) {
if (stat instanceof AST_For) {
try {
if (stat instanceof AST_For && !(stat.init instanceof AST_Definitions)) {
var abort = false;
prev.body.walk(new TreeWalker(function(node) {
if (node instanceof AST_Binary && node.operator == "in")
throw cons_seq;
}));
if (stat.init && !(stat.init instanceof AST_Definitions)) {
stat.init = cons_seq(stat.init);
if (abort || node instanceof AST_Scope) return true;
if (node instanceof AST_Binary && node.operator == "in") {
abort = true;
return true;
}
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);
n--;
}
} catch(ex) {
if (ex !== cons_seq) throw ex;
}
}
else if (stat instanceof AST_If) {
@@ -1490,13 +1544,8 @@ merge(Compressor.prototype, {
// descendant of AST_Node.
AST_Node.DEFMETHOD("evaluate", function(compressor){
if (!compressor.option("evaluate")) return this;
try {
var val = this._eval(compressor);
return !val || val instanceof RegExp || typeof val != "object" ? val : this;
} catch(ex) {
if (ex !== def) throw ex;
return this;
}
});
var unaryPrefix = makePredicate("! ~ - + void");
AST_Node.DEFMETHOD("is_constant", function(){
@@ -1542,27 +1591,28 @@ merge(Compressor.prototype, {
def(AST_Statement, function(){
throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
});
def(AST_Lambda, function(){
throw def;
});
def(AST_Lambda, return_this);
function ev(node, compressor) {
if (!compressor) throw new Error("Compressor must be passed");
return node._eval(compressor);
};
def(AST_Node, function(){
throw def; // not constant
});
def(AST_Node, return_this);
def(AST_Constant, function(){
return this.getValue();
});
def(AST_Array, function(compressor){
if (compressor.option("unsafe")) {
return this.elements.map(function(element) {
return ev(element, compressor);
});
var elements = [];
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);
}
throw def;
return elements;
}
return this;
});
def(AST_Object, function(compressor){
if (compressor.option("unsafe")) {
@@ -1574,163 +1624,262 @@ merge(Compressor.prototype, {
key = key.name;
} else if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === prop.key) return this;
}
if (typeof Object.prototype[key] === 'function') {
throw def;
return this;
}
val[key] = ev(prop.value, compressor);
if (val[key] === prop.value) return this;
}
return val;
}
throw def;
return this;
});
def(AST_UnaryPrefix, function(compressor){
var e = this.expression;
switch (this.operator) {
case "!": return !ev(e, compressor);
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);
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) {
case "!": return !e;
case "typeof":
// typeof <RegExp> returns "object" or "function" on different platforms
// so cannot evaluate reliably
if (e instanceof RegExp) throw def;
if (e instanceof RegExp) return this;
return typeof e;
case "void": return void ev(e, compressor);
case "~": return ~ev(e, compressor);
case "-": return -ev(e, compressor);
case "+": return +ev(e, compressor);
case "void": return void e;
case "~": return ~e;
case "-": return -e;
case "+": return +e;
}
throw def;
return this;
});
def(AST_Binary, function(c){
var left = this.left, right = this.right, result;
def(AST_Binary, function(compressor){
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) {
case "&&" : result = ev(left, c) && ev(right, c); break;
case "||" : result = ev(left, c) || ev(right, c); break;
case "|" : result = ev(left, c) | ev(right, c); break;
case "&" : result = ev(left, c) & ev(right, c); break;
case "^" : result = ev(left, c) ^ ev(right, c); break;
case "+" : result = ev(left, c) + ev(right, c); break;
case "*" : result = ev(left, c) * ev(right, c); break;
case "/" : result = ev(left, c) / ev(right, c); break;
case "%" : result = ev(left, c) % ev(right, c); break;
case "-" : result = ev(left, c) - ev(right, c); break;
case "<<" : result = ev(left, c) << ev(right, c); break;
case ">>" : result = ev(left, c) >> ev(right, c); break;
case ">>>" : result = ev(left, c) >>> ev(right, c); break;
case "==" : result = ev(left, c) == ev(right, c); break;
case "===" : result = ev(left, c) === ev(right, c); break;
case "!=" : result = ev(left, c) != ev(right, c); break;
case "!==" : result = ev(left, c) !== ev(right, c); break;
case "<" : result = ev(left, c) < ev(right, c); break;
case "<=" : result = ev(left, c) <= ev(right, c); break;
case ">" : result = ev(left, c) > ev(right, c); break;
case ">=" : result = ev(left, c) >= ev(right, c); break;
case "&&" : result = left && right; break;
case "||" : result = left || right; break;
case "|" : result = left | right; break;
case "&" : result = left & right; break;
case "^" : result = left ^ right; break;
case "+" : result = left + right; break;
case "*" : result = left * right; break;
case "/" : result = left / right; break;
case "%" : result = left % right; break;
case "-" : result = left - right; break;
case "<<" : result = left << right; break;
case ">>" : result = left >> right; break;
case ">>>" : result = left >>> right; break;
case "==" : result = left == right; break;
case "===" : result = left === right; break;
case "!=" : result = left != right; break;
case "!==" : result = left !== right; break;
case "<" : result = left < right; break;
case "<=" : result = left <= right; break;
case ">" : result = left > right; break;
case ">=" : result = left >= right; break;
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
throw def;
return this;
}
return result;
});
def(AST_Conditional, function(compressor){
return ev(this.condition, compressor)
? ev(this.consequent, compressor)
: ev(this.alternative, compressor);
var condition = ev(this.condition, compressor);
if (condition === this.condition) return this;
var node = condition ? this.consequent : this.alternative;
var value = ev(node, compressor);
return value === node ? this : value;
});
def(AST_SymbolRef, function(compressor){
if (!compressor.option("reduce_vars") || this._evaluating) throw def;
this._evaluating = true;
try {
if (!compressor.option("reduce_vars")) return this;
var fixed = this.fixed_value();
if (!fixed) throw def;
if (!fixed) return this;
this._eval = return_this;
var value = ev(fixed, compressor);
if (value === fixed) {
delete this._eval;
return this;
}
if (!HOP(fixed, "_eval")) fixed._eval = function() {
return value;
};
if (value && typeof value == "object" && this.definition().escaped) throw def;
return value;
} finally {
this._evaluating = false;
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){
if (compressor.option("unsafe")) {
var key = this.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === this.property) return this;
}
var exp = this.expression;
var val;
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;
}
var val = ev(this.expression, compressor);
if (val && HOP(val, key)) {
return val[key];
}
}
throw def;
return this;
});
var object_fns = [
'constructor',
'toString',
'valueOf',
"constructor",
"toString",
"valueOf",
];
var native_fns = {
Array: makePredicate([
'indexOf',
'join',
'lastIndexOf',
'slice',
].concat(object_fns)),
Boolean: makePredicate(object_fns),
Number: makePredicate([
'toExponential',
'toFixed',
'toPrecision',
].concat(object_fns)),
RegExp: makePredicate([
'test',
].concat(object_fns)),
String: makePredicate([
'charAt',
'charCodeAt',
'concat',
'indexOf',
'italics',
'lastIndexOf',
'match',
'replace',
'search',
'slice',
'split',
'substr',
'substring',
'trim',
].concat(object_fns)),
Array: [
"indexOf",
"join",
"lastIndexOf",
"slice",
].concat(object_fns),
Boolean: object_fns,
Number: [
"toExponential",
"toFixed",
"toPrecision",
].concat(object_fns),
RegExp: [
"test",
].concat(object_fns),
String: [
"charAt",
"charCodeAt",
"concat",
"indexOf",
"italics",
"lastIndexOf",
"match",
"replace",
"search",
"slice",
"split",
"substr",
"substring",
"trim",
].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){
var exp = this.expression;
if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
var key = exp.property;
if (key instanceof AST_Node) {
key = ev(key, compressor);
if (key === exp.property) return this;
}
var val = ev(exp.expression, compressor);
if ((val && native_fns[val.constructor.name] || return_false)(key)) {
return val[key].apply(val, this.args.map(function(arg) {
return ev(arg, compressor);
}));
var val;
var e = exp.expression;
if (e instanceof AST_SymbolRef && e.undeclared()) {
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);
}
throw def;
});
def(AST_New, function(compressor){
throw def;
return val[key].apply(val, args);
}
return this;
});
def(AST_New, return_this);
})(function(node, func){
node.DEFMETHOD("_eval", func);
});
@@ -3123,7 +3272,7 @@ merge(Compressor.prototype, {
// https://github.com/mishoo/UglifyJS2/issues/203
// if the code argument is a constant, then we can minify it.
try {
var code = "NaN(function(" + self.args.slice(0, -1).map(function(arg) {
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
return arg.value;
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
var ast = parse(code);
@@ -3143,18 +3292,18 @@ merge(Compressor.prototype, {
return true;
}
}));
var args = fun.argnames.map(function(arg, i) {
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
var code = OutputStream();
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
code = code.toString().replace(/^\{|\}$/g, "");
args.push(make_node(AST_String, self.args[self.args.length - 1], {
value: code
}));
self.args = args;
self.args = [
make_node(AST_String, self, {
value: fun.argnames.map(function(arg) {
return arg.print_to_string();
}).join(",")
}),
make_node(AST_String, self.args[self.args.length - 1], {
value: code.get().replace(/^\{|\}$/g, "")
})
];
return self;
} catch (ex) {
if (ex instanceof JS_Parse_Error) {
@@ -3171,80 +3320,53 @@ merge(Compressor.prototype, {
var value = stat.value;
if (!value || value.is_constant_expression()) {
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 (compressor.option("inline")
&& !exp.name
&& exp.body.length == 1
&& !exp.uses_arguments
&& !exp.uses_eval
&& exp.body.length == 1
&& all(exp.argnames, function(arg) {
return arg.__unused;
})
&& !self.has_pure_annotation(compressor)) {
var value;
if (stat instanceof AST_Return) {
value = stat.value.clone(true);
value = stat.value;
} else if (stat instanceof AST_SimpleStatement) {
value = make_node(AST_UnaryPrefix, stat, {
operator: "void",
expression: stat.body.clone(true)
expression: stat.body
});
}
if (value) {
var fn = exp.clone();
fn.argnames = [];
fn.body = [];
if (exp.argnames.length > 0) {
fn.body.push(make_node(AST_Var, self, {
definitions: exp.argnames.map(function(sym, i) {
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 (!value) 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;
value = null;
return true;
}
}
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
value = self;
value = null;
return true;
}
});
value.walk(tw);
if (value !== self) value = best_of(compressor, value, self);
} else {
value = self;
}
if (value !== self) return value;
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)) {
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")) {
@@ -3562,7 +3684,8 @@ merge(Compressor.prototype, {
case "==":
case "!=":
// "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.right instanceof AST_UnaryPrefix
&& self.right.operator == "typeof") {
@@ -4269,6 +4392,17 @@ merge(Compressor.prototype, {
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){
var def = self.resolve_defines(compressor);
if (def) {
@@ -4283,6 +4417,20 @@ merge(Compressor.prototype, {
})
}).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")
&& self.expression instanceof AST_Dot
&& 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) {
var warn_function = AST_Node.warn_function;
try {
@@ -35,6 +52,7 @@ function minify(files, options) {
ie8: false,
keep_fnames: false,
mangle: {},
nameCache: null,
output: {},
parse: {},
sourceMap: false,
@@ -52,7 +70,7 @@ function minify(files, options) {
set_shorthand("warnings", options, [ "compress" ]);
if (options.mangle) {
options.mangle = defaults(options.mangle, {
cache: null,
cache: options.nameCache && (options.nameCache.vars || {}),
eval: false,
ie8: false,
keep_fnames: false,
@@ -60,6 +78,16 @@ function minify(files, options) {
reserved: [],
toplevel: false,
}, 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) {
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) {
timings.end = Date.now();
result.timings = {

View File

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

View File

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

View File

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

View File

@@ -1978,10 +1978,10 @@ chained_3: {
}
expect: {
console.log(function(a, b) {
var c = a, c = b;
var c = 1, c = b;
b++;
return c;
}(1, 2));
}(0, 2));
}
expect_stdout: "2"
}
@@ -2186,3 +2186,159 @@ compound_assignment: {
}
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 = {
collapse_vars: true,
inline: true,
passes: 3,
reduce_vars: true,
side_effects: true,
unused: true,
@@ -1138,7 +1139,7 @@ issue_2105: {
});
}
expect: {
!void function() {
(function() {
var quux = function() {
console.log("PASS");
};
@@ -1148,7 +1149,7 @@ issue_2105: {
quux();
}
};
}().prop();
})().prop();
}
expect_stdout: "PASS"
}

View File

@@ -251,21 +251,25 @@ unsafe_constant: {
unsafe_object: {
options = {
evaluate: true,
unsafe : true
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var o = { a: 1 };
console.log(
({a:1}) + 1,
({a:1}).a + 1,
({a:1}).b + 1,
({a:1}).a.b + 1
o + 1,
o.a + 1,
o.b + 1,
o.a.b + 1
);
}
expect: {
var o = { a: 1 };
console.log(
({a:1}) + 1,
o + 1,
2,
({a:1}).b + 1,
o.b + 1,
1..b + 1
);
}
@@ -275,21 +279,25 @@ unsafe_object: {
unsafe_object_nested: {
options = {
evaluate: true,
unsafe : true
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var o = { a: { b: 1 } };
console.log(
({a:{b:1}}) + 1,
({a:{b:1}}).a + 1,
({a:{b:1}}).b + 1,
({a:{b:1}}).a.b + 1
o + 1,
o.a + 1,
o.b + 1,
o.a.b + 1
);
}
expect: {
var o = { a: { b: 1 } };
console.log(
({a:{b:1}}) + 1,
({a:{b:1}}).a + 1,
({a:{b:1}}).b + 1,
o + 1,
o.a + 1,
o.b + 1,
2
);
}
@@ -299,20 +307,24 @@ unsafe_object_nested: {
unsafe_object_complex: {
options = {
evaluate: true,
unsafe : true
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var o = { a: { b: 1 }, b: 1 };
console.log(
({a:{b:1},b:1}) + 1,
({a:{b:1},b:1}).a + 1,
({a:{b:1},b:1}).b + 1,
({a:{b:1},b:1}).a.b + 1
o + 1,
o.a + 1,
o.b + 1,
o.a.b + 1
);
}
expect: {
var o = { a: { b: 1 }, b: 1 };
console.log(
({a:{b:1},b:1}) + 1,
({a:{b:1},b:1}).a + 1,
o + 1,
o.a + 1,
2,
2
);
@@ -323,21 +335,25 @@ unsafe_object_complex: {
unsafe_object_repeated: {
options = {
evaluate: true,
unsafe : true
reduce_vars: true,
toplevel: true,
unsafe: true,
}
input: {
var o = { a: { b: 1 }, a: 1 };
console.log(
({a:{b:1},a:1}) + 1,
({a:{b:1},a:1}).a + 1,
({a:{b:1},a:1}).b + 1,
({a:{b:1},a:1}).a.b + 1
o + 1,
o.a + 1,
o.b + 1,
o.a.b + 1
);
}
expect: {
var o = { a: { b: 1 }, a: 1 };
console.log(
({a:{b:1},a:1}) + 1,
o + 1,
2,
({a:{b:1},a:1}).b + 1,
o.b + 1,
1..b + 1
);
}
@@ -386,9 +402,9 @@ unsafe_function: {
expect: {
console.log(
({a:{b:1},b:function(){}}) + 1,
({a:{b:1},b:function(){}}).a + 1,
({a:{b:1},b:function(){}}).b + 1,
({a:{b:1},b:function(){}}).a.b + 1
({b:function(){}}, {b:1}) + 1,
({a:{b:1}}, function(){}) + 1,
({b:function(){}}, {b:1}).b + 1
);
}
expect_stdout: true
@@ -636,8 +652,8 @@ unsafe_prototype_function: {
var d = ({toString: 0}) + "";
var e = (({valueOf: 0}) + "")[2];
var f = (({toString: 0}) + "")[2];
var g = ({valueOf: 0}).valueOf();
var h = "" + ({toString: 0});
var g = ({}, 0)();
var h = ({}, 0)();
}
}
@@ -1069,3 +1085,75 @@ string_charCodeAt: {
}
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

@@ -265,7 +265,7 @@ issue_203: {
}
expect: {
var m = {};
var fn = Function("n", "o", "o.exports=42");
var fn = Function("n,o", "o.exports=42");
fn(null, m, m.exports);
console.log(m.exports);
}
@@ -468,11 +468,9 @@ issue_2114_1: {
}
expect: {
var c = 0;
!function() {
0;
}((c += 1, c = 1 + c, function() {
c = 1 + (c += 1), function() {
var b = void (b && (b.b += (c += 1, 0)));
}()));
}();
console.log(c);
}
expect_stdout: "2"

View File

@@ -37,6 +37,7 @@ object: {
VALUE: 42,
},
},
side_effects: true,
unsafe: true,
}
input: {
@@ -140,9 +141,9 @@ mixed: {
console.log(CONFIG);
}
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: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]',
]
}
@@ -174,3 +175,24 @@ issue_1986: {
console.log(42);
}
}
issue_2167: {
options = {
conditionals: true,
dead_code: true,
evaluate: true,
global_defs: {
"@isDevMode": "function(){}",
},
side_effects: true,
}
input: {
if (isDevMode()) {
greetOverlord();
}
doWork();
}
expect: {
doWork();
}
}

View File

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

View File

@@ -419,7 +419,7 @@ wrap_iife_in_return_call: {
expect_exact: '(void console.log("test"))();'
}
pure_annotation: {
pure_annotation_1: {
options = {
inline: true,
side_effects: true,
@@ -432,6 +432,20 @@ pure_annotation: {
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: {
options = {
cascade: true,
@@ -449,9 +463,7 @@ drop_fargs: {
}
expect: {
var a = 1;
!function() {
a++;
}(++a && a.var);
++a && a.var, a++;
console.log(a);
}
expect_stdout: "3"
@@ -474,9 +486,7 @@ keep_fargs: {
}
expect: {
var a = 1;
!function(a_1) {
a++;
}(++a && a.var);
++a && a.var, a++;
console.log(a);
}
expect_stdout: "3"

View File

@@ -436,3 +436,17 @@ do_switch: {
} 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);';
}
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_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,
side_effects: true,
switches: true,
typeofs: true,
unused: true,
}
input: {
@@ -1532,6 +1533,7 @@ issue_1670_3: {
reduce_vars: true,
side_effects: true,
switches: true,
typeofs: true,
unused: true,
}
input: {

View File

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

View File

@@ -1,6 +1,7 @@
var Uglify = require('../../');
var assert = require("assert");
var readFileSync = require("fs").readFileSync;
var run_code = require("../sandbox").run_code;
function read(path) {
return readFileSync(path, "utf8");
@@ -20,6 +21,58 @@ describe("minify", function() {
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() {
it("Should preserve quotes in object literals", function() {
var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';
@@ -212,7 +265,7 @@ describe("minify", function() {
});
var err = result.error;
assert.ok(err instanceof Error);
assert.strictEqual(err.stack.split(/\n/)[0], "Error: Can't handle expression: debugger");
assert.strictEqual(err.stack.split(/\n/)[0], "SyntaxError: Unexpected token: keyword (debugger)");
});
it("should skip inherited properties", function() {
var foo = Object.create({ skip: this });