Compare commits

...

25 Commits

Author SHA1 Message Date
Alex Lam S.L
33ad0d258c harmony-v3.0.23 2017-07-02 19:04:15 +08:00
alexlamsl
5ea1da2d42 handle AST_Expansion in collapse_vars & inline 2017-07-02 18:15:16 +08:00
alexlamsl
e77b6d525c Merge branch 'master' into harmony-v3.0.23 2017-07-02 17:47:21 +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
a9eecd844f harmony-v3.0.22 2017-06-30 12:56:56 +08:00
alexlamsl
ed3032e52a Merge branch 'master' into harmony-v3.0.22 2017-06-30 11:24:07 +08:00
Alex Lam S.L
7659ea1d2e v3.0.22 2017-06-30 11:18:34 +08:00
Alex Lam S.L
52cc21d999 remove extraneous ! before AST_Arrow (#2185) 2017-06-30 11:17:58 +08:00
kzc
a938fe5e1f async arrow function IIFE fix (#2184)
fixes #2183
2017-06-30 10:12:42 +08:00
kzc
07a5a57336 fix await parens for property access and calls (#2181)
fixes #2179
2017-06-30 09:14:24 +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
945db924fc Merge pull request #2177 from alexlamsl/harmony-v3.0.21
Merging from master for 3.0.21
2017-06-29 02:37:28 +08:00
alexlamsl
087bce508a Merge branch 'master' into harmony-v3.0.21 2017-06-29 00:58:28 +08:00
Alex Lam S.L
5e6f26445f v3.0.21 2017-06-29 00:49:06 +08:00
kzc
fc7e33453f [ES6] document mangle option keep_classnames (#2175) 2017-06-28 23:51:58 +08:00
Alex Lam S.L
d052394621 fix line terminators in template literals (#2173)
fixes #2172
2017-06-28 22:52:29 +08:00
Alex Lam S.L
4d5aeeddfb compress AST_Arrow properly (#2170) 2017-06-28 01:06:30 +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
Alex Lam S.L
ad139aa34d fix side_effects on AST_Expansion (#2165)
fixes #2163
2017-06-27 01:13:00 +08:00
kzc
26be15f111 update uglify-es keywords in package.json (#2168) 2017-06-27 00:56:01 +08:00
kzc
179f33f08a update doc notes for uglify-es (#2164) 2017-06-26 11:04:22 +08:00
kzc
d260fe9018 more documentation for the ecma option (#2162) 2017-06-26 02:39:14 +08:00
19 changed files with 797 additions and 168 deletions

View File

@@ -1,11 +1,11 @@
uglify-es
=========
**uglify-es** is an ECMAScript 2015 parser, minifier, compressor and beautifier toolkit.
**uglify-es** is an ECMAScript parser, minifier, compressor and beautifier toolkit for ES6+.
#### Note:
- **The `uglify-es` API and CLI is compatible with `uglify-js@3.x`.**
- **`uglify-es` is not backwards compatible with the `uglify-js@2.x` API and CLI.**
- **`uglify-es` is API/CLI compatible with `uglify-js@3`.**
- **`uglify-es` is not backwards compatible with `uglify-js@2`.**
Install
-------
@@ -109,7 +109,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.
@@ -381,7 +381,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:
@@ -434,6 +474,9 @@ if (result.error) throw result.error;
## Minify options
- `ecma` (default `undefined`) - pass `5`, `6`, `7` or `8` to override `parse`,
`compress` and `output` options.
- `warnings` (default `false`) — pass `true` to return compressor warnings
in `result.warnings`. Use the value `"verbose"` for more detailed warnings.
@@ -459,13 +502,19 @@ 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
```javascript
{
warnings: false,
parse: {
// parse options
},
@@ -485,8 +534,11 @@ if (result.error) throw result.error;
sourceMap: {
// source map options
},
ecma: 5, // specify one of: 5, 6, 7 or 8
nameCache: null, // or specify a name cache object
toplevel: false,
ie8: false,
warnings: false,
}
```
@@ -540,6 +592,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
## Parse options
- `bare_returns` (default `false`) -- support top level `return` statements
- `ecma` (default: `8`) -- specify one of `5`, `6`, `7` or `8`. Note: this setting
is not presently enforced except for ES8 optional trailing commas in function
parameter lists and calls with `ecma` `8`.
- `html5_comments` (default `true`)
- `shebang` (default `true`) -- support `#!command` as the first line
@@ -683,6 +738,9 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
annotation `/*@__PURE__*/` or `/*#__PURE__*/` immediately precedes the call. For
example: `/*@__PURE__*/foo();`
- `ecma` -- default `5`. Pass `6` or greater to enable `compress` options that
will transform ES5 code into smaller ES6+ equivalent forms.
## Mangle options
- `reserved` (default `[]`). Pass an array of identifiers that should be
@@ -691,6 +749,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `toplevel` (default `false`). Pass `true` to mangle names declared in the
top level scope.
- `keep_classnames` (default `false`). Pass `true` to not mangle class names.
- `keep_fnames` (default `false`). Pass `true` to not mangle function names.
Useful for code relying on `Function.prototype.name`. See also: the `keep_fnames`
[compress option](#compress-options).
@@ -754,9 +814,12 @@ can pass additional arguments that control the code output:
- `comments` (default `false`) -- pass `true` or `"all"` to preserve all
comments, `"some"` to preserve some comments, a regular expression string
(e.g. `/^!/`) or a function.
- `ecma` (default `5`) -- set output printing mode. This will only change the
output in direct control of the beautifier. Non-compatible features in the
abstract syntax tree will still be outputted as is.
- `ecma` (default `5`) -- set output printing mode. Set `ecma` to `6` or
greater to emit shorthand object properties - i.e.: `{a}` instead of `{a: a}`.
The `ecma` option will only change the output in direct control of the
beautifier. Non-compatible features in the abstract syntax tree will still
be output as is. For example: an `ecma` setting of `5` will **not** convert
ES6+ code to ES5.
- `indent_level` (default 4)
- `indent_start` (default 0) -- prefix all lines by that many spaces
- `inline_script` (default `false`) -- escape the slash in occurrences of

View File

@@ -111,17 +111,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 = {
@@ -271,9 +262,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");
@@ -386,18 +375,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

@@ -345,7 +345,7 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
var AST_Expansion = DEFNODE("Expansion", "expression", {
$documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list",
$propdoc: {
expression: "AST_Symbol the thing to be expanded"
expression: "[AST_Node] the thing to be expanded"
},
_walk: function(visitor) {
var self = this;
@@ -446,7 +446,7 @@ var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_str
var AST_TemplateString = DEFNODE("TemplateString", "segments", {
$documentation: "A template string literal",
$propdoc: {
segments: "[AST_TemplateSegment|AST_Expression]* One or more segments, starting with AST_TemplateSegment. AST_Expression may follow AST_TemplateSegment, but each AST_Expression must be followed by AST_TemplateSegment."
segments: "[AST_Node*] One or more segments, starting with AST_TemplateSegment. AST_Node may follow AST_TemplateSegment, but each AST_Node must be followed by AST_TemplateSegment."
},
_walk: function(visitor) {
return visitor._visit(this, function(){

View File

@@ -94,12 +94,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"];
@@ -366,7 +363,7 @@ merge(Compressor.prototype, {
safe_ids = save_ids;
return true;
}
if (node instanceof AST_Function || node instanceof AST_Arrow) {
if (is_func_expr(node)) {
push();
var iife;
if (!node.name
@@ -564,6 +561,10 @@ merge(Compressor.prototype, {
return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
});
function is_func_expr(node) {
return node instanceof AST_Arrow || node instanceof AST_Function;
}
function is_lhs_read_only(lhs) {
if (lhs instanceof AST_SymbolRef) return lhs.definition().orig[0] instanceof AST_SymbolLambda;
if (lhs instanceof AST_PropAccess) {
@@ -748,15 +749,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
@@ -839,6 +848,58 @@ merge(Compressor.prototype, {
}
}
function has_overlapping_symbol(fn, arg) {
var found = false;
arg.walk(new TreeWalker(function(node) {
if (found) 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;
}
found = true;
}
}));
return found;
}
function extract_args() {
var iife, fn = compressor.self();
if (is_func_expr(fn)
&& !fn.name
&& !fn.uses_arguments
&& !fn.uses_eval
&& (iife = compressor.parent()) instanceof AST_Call
&& iife.expression === fn
&& all(iife.args, function(arg) {
return !(arg instanceof AST_Expansion);
})) {
fn.argnames.forEach(function(sym, i) {
if (sym instanceof AST_Expansion) {
var elements = iife.args.slice(i);
if (all(elements, function(arg) {
return !has_overlapping_symbol(fn, arg);
})) {
candidates.push(make_node(AST_VarDef, sym, {
name: sym.expression,
value: make_node(AST_Array, iife, {
elements: elements
})
}));
}
} else {
var arg = iife.args[i];
if (!arg) arg = make_node(AST_Undefined, sym);
else if (has_overlapping_symbol(fn, arg)) arg = null;
if (arg) candidates.push(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 == "--")) {
@@ -859,7 +920,7 @@ merge(Compressor.prototype, {
function get_lhs(expr) {
if (expr instanceof AST_VarDef && expr.name instanceof AST_SymbolDeclaration) {
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);
}
@@ -902,6 +963,19 @@ merge(Compressor.prototype, {
}
function remove_candidate(expr) {
if (expr.name instanceof AST_SymbolFunarg) {
var iife = compressor.parent(), argnames = compressor.self().argnames;
var index = argnames.indexOf(expr.name);
if (index < 0) {
iife.args.length = Math.min(iife.args.length, argnames.length - 1);
} else {
var args = iife.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;
@@ -1320,6 +1394,7 @@ merge(Compressor.prototype, {
return false;
});
def(AST_Function, return_false);
def(AST_Arrow, return_false);
def(AST_UnaryPostfix, return_false);
def(AST_UnaryPrefix, function() {
return this.operator == "void";
@@ -1593,9 +1668,6 @@ merge(Compressor.prototype, {
def(AST_Lambda, function(){
throw def;
});
def(AST_Arrow, function() {
throw def;
});
def(AST_Class, function() {
throw def;
});
@@ -1649,8 +1721,7 @@ merge(Compressor.prototype, {
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 ||
e instanceof AST_Arrow) return typeof function(){};
if (is_func_expr(e)) return typeof function(){};
e = ev(e, compressor);
@@ -1822,6 +1893,9 @@ merge(Compressor.prototype, {
def(AST_Function, function(){
return basic_negation(this);
});
def(AST_Arrow, function(){
return basic_negation(this);
});
def(AST_UnaryPrefix, function(){
if (this.operator == "!")
return this.expression;
@@ -2568,7 +2642,7 @@ merge(Compressor.prototype, {
def(AST_This, return_null);
def(AST_Call, function(compressor, first_in_statement){
if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) {
if (this.expression instanceof AST_Function
if (is_func_expr(this.expression)
&& (!this.expression.name || !this.expression.name.definition().references.length)) {
var node = this.clone();
node.expression.process_expression(false, compressor);
@@ -2672,6 +2746,9 @@ merge(Compressor.prototype, {
if (expr) merge_sequence(expressions, expr);
return make_sequence(this, expressions);
});
def(AST_Expansion, function(compressor, first_in_statement){
return this.expression.drop_side_effect_free(compressor, first_in_statement);
});
})(function(node, func){
node.DEFMETHOD("drop_side_effect_free", func);
});
@@ -3092,16 +3169,16 @@ merge(Compressor.prototype, {
});
if (compressor.option("unused")
&& simple_args
&& (fn instanceof AST_Function
&& (is_func_expr(fn)
|| compressor.option("reduce_vars")
&& fn instanceof AST_SymbolRef
&& (fn = fn.fixed_value()) instanceof AST_Function)
&& is_func_expr(fn = fn.fixed_value()))
&& !fn.uses_arguments
&& !fn.uses_eval) {
var pos = 0, last = 0;
for (var i = 0, len = self.args.length; i < len; i++) {
if (fn.argnames[i] instanceof AST_Expansion) {
if (fn.argnames[i].__unused) while (i < len) {
if (fn.argnames[i].expression.__unused) while (i < len) {
var node = self.args[i++].drop_side_effect_free(compressor);
if (node) {
self.args[pos++] = node;
@@ -3269,7 +3346,7 @@ merge(Compressor.prototype, {
if (self.args.length == 0) return make_node(AST_Function, self, {
argnames: [],
body: []
});
}).optimize(compressor);
if (all(self.args, function(x) {
return x instanceof AST_String;
})) {
@@ -3277,7 +3354,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);
@@ -3292,24 +3369,30 @@ merge(Compressor.prototype, {
var fun;
ast.walk(new TreeWalker(function(node) {
if (fun) return true;
if (node instanceof AST_Function) {
if (is_func_expr(node)) {
fun = node;
return true;
}
}));
if (!fun) return self;
var args = fun.argnames.map(function(arg, i) {
return make_node(AST_String, self.args[i], {
value: arg.print_to_string()
});
});
if (fun.body instanceof AST_Node) {
fun.body = [
make_node(AST_Return, fun.body, {
value: fun.body
})
];
}
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) {
@@ -3321,96 +3404,67 @@ merge(Compressor.prototype, {
}
}
}
var stat = fn instanceof AST_Function && fn.body[0];
var stat = is_func_expr(fn) && fn.body;
if (stat instanceof AST_Node) {
stat = make_node(AST_Return, stat, {
value: stat
});
} else if (stat) {
stat = stat[0];
}
if (compressor.option("inline") && stat instanceof AST_Return) {
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 && !exp.is_generator && !exp.async) {
if (is_func_expr(exp) && !exp.is_generator && !exp.async) {
if (compressor.option("inline")
&& simple_args
&& !exp.name
&& exp.body.length == 1
&& !exp.uses_arguments
&& !exp.uses_eval
&& simple_args
&& (exp.body instanceof AST_Node || exp.body.length == 1)
&& all(exp.argnames, function(arg) {
if (arg instanceof AST_Expansion) return arg.expression.__unused;
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) {
if (sym instanceof AST_Expansion) {
return make_node(AST_VarDef, sym, {
name: sym.expression,
value: make_node(AST_Array, self, {
elements: self.args.slice(i).map(function(arg) {
return arg.clone(true);
})
})
});
}
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 && !(exp.argnames[exp.argnames.length - 1] instanceof AST_Expansion)) {
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;
var tw = new TreeWalker(function(node) {
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 = null;
return true;
}
});
value.walk(tw);
if (value !== self) value = best_of(compressor, value, self);
} else {
value = self;
}
if (value !== self) return value;
}
if (node instanceof AST_This && !tw.find_parent(AST_Scope)) {
value = null;
return true;
}
});
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") && !(exp.body instanceof AST_Node) && 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")) {
@@ -4082,7 +4136,7 @@ merge(Compressor.prototype, {
d.fixed = fixed = make_node(AST_Function, fixed, fixed);
}
if (compressor.option("unused")
&& fixed instanceof AST_Function
&& is_func_expr(fixed)
&& d.references.length == 1
&& !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
&& !d.scope.uses_eval

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 {
@@ -36,6 +53,7 @@ function minify(files, options) {
ie8: false,
keep_fnames: false,
mangle: {},
nameCache: null,
output: {},
parse: {},
sourceMap: false,
@@ -54,7 +72,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_classnames: false,
@@ -64,6 +82,16 @@ function minify(files, options) {
safari10: false,
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, {
@@ -157,6 +185,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

@@ -673,6 +673,12 @@ function OutputStream(options) {
&& this.operator !== "--";
});
PARENS(AST_Await, function(output){
var p = output.parent();
return p instanceof AST_PropAccess && p.expression === this
|| p instanceof AST_Call && p.expression === this;
});
PARENS(AST_Sequence, function(output){
var p = output.parent();
return p instanceof AST_Call // (foo, bar)() or foo(1, (2, 3), 4)
@@ -1039,11 +1045,11 @@ function OutputStream(options) {
var needs_parens = parent instanceof AST_Binary ||
parent instanceof AST_Unary ||
(parent instanceof AST_Call && self === parent.expression);
if (needs_parens) { output.print("(") }
if (self.async) {
output.print("async");
output.space();
}
if (needs_parens) { output.print("(") }
if (self.argnames.length === 1 && self.argnames[0] instanceof AST_Symbol) {
self.argnames[0].print(output);
} else {

View File

@@ -509,8 +509,11 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
var content = "", raw = "", ch, tok;
next(true, true);
while ((ch = next(true, true)) !== "`") {
if (ch === "$" && peek() === "{") {
while ((ch = next(true, true)) != "`") {
if (ch == "\r") {
if (peek() == "\n") ++S.pos;
ch = "\n";
} else if (ch == "$" && peek() == "{") {
next(true, true);
S.brace_counter++;
tok = token(begin ? "template_head" : "template_substitution", content);
@@ -521,7 +524,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
raw += ch;
if (ch === "\\") {
if (ch == "\\") {
var tmp = S.pos;
ch = read_escaped_char();
raw += S.text.substr(tmp, S.pos - tmp);

View File

@@ -4,7 +4,7 @@
"homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony",
"author": "Mihai Bazon <mihai.bazon@gmail.com> (http://lisperator.net/)",
"license": "BSD-2-Clause",
"version": "3.0.20",
"version": "3.0.23",
"engines": {
"node": ">=0.8.0"
},
@@ -40,5 +40,18 @@
"scripts": {
"test": "node test/run-tests.js"
},
"keywords": ["uglify", "uglify-js", "uglify-es", "minify", "minifier", "es5", "es6", "es2015"]
"keywords": [
"uglify",
"uglify-js",
"uglify-es",
"minify",
"minifier",
"es5",
"es6",
"es2015",
"es2016",
"es2017",
"async",
"await"
]
}

View File

@@ -280,3 +280,263 @@ issue_27: {
})(jQuery);
}
}
issue_2105_1: {
options = {
arrows: true,
collapse_vars: true,
ecma: 6,
inline: true,
passes: 3,
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
!function(factory) {
factory();
}( function() {
return function(fn) {
fn()().prop();
}( function() {
function bar() {
var quux = function() {
console.log("PASS");
}, foo = function() {
console.log;
quux();
};
return { prop: foo };
}
return bar;
} );
});
}
expect: {
(() => {
var quux = () => {
console.log("PASS");
};
return {
prop: () => {
console.log;
quux();
}
};
})().prop();
}
expect_stdout: "PASS"
node_version: ">=4"
}
issue_2105_2: {
options = {
collapse_vars: true,
inline: true,
passes: 2,
reduce_vars: true,
side_effects: true,
unused: true,
}
input: {
((factory) => {
factory();
})( () => {
return ((fn) => {
fn()().prop();
})( () => {
let bar = () => {
var quux = () => {
console.log("PASS");
}, foo = () => {
console.log;
quux();
};
return { prop: foo };
};
return bar;
} );
});
}
expect: {
(() => {
var quux = () => {
console.log("PASS");
};
return {
prop: () => {
console.log;
quux();
}
};
})().prop();
}
expect_stdout: "PASS"
node_version: ">=6"
}
issue_2136_2: {
options = {
arrows: true,
collapse_vars: true,
ecma: 6,
inline: true,
side_effects: true,
unused: true,
}
input: {
function f(x) {
console.log(x);
}
!function(a, ...b) {
f(b[0]);
}(1, 2, 3);
}
expect: {
function f(x) {
console.log(x);
}
f([2,3][0]);
}
expect_stdout: "2"
node_version: ">=6"
}
issue_2136_3: {
options = {
arrows: true,
collapse_vars: true,
ecma: 6,
evaluate: true,
inline: true,
passes: 3,
reduce_vars: true,
side_effects: true,
toplevel: true,
unsafe: true,
unused: true,
}
input: {
function f(x) {
console.log(x);
}
!function(a, ...b) {
f(b[0]);
}(1, 2, 3);
}
expect: {
console.log(2);
}
expect_stdout: "2"
node_version: ">=6"
}
call_args: {
options = {
arrows: true,
ecma: 6,
evaluate: true,
inline: true,
reduce_vars: true,
}
input: {
const a = 1;
console.log(a);
+function(a) {
return a;
}(a);
}
expect: {
const a = 1;
console.log(1);
+(1, 1);
}
expect_stdout: true
}
call_args_drop_param: {
options = {
arrows: true,
ecma: 6,
evaluate: true,
inline: true,
keep_fargs: false,
reduce_vars: true,
unused: true,
}
input: {
const a = 1;
console.log(a);
+function(a) {
return a;
}(a, b);
}
expect: {
const a = 1;
console.log(1);
+(b, 1);
}
expect_stdout: true
}
issue_485_crashing_1530: {
options = {
arrows: true,
conditionals: true,
dead_code: true,
ecma: 6,
evaluate: true,
inline: true,
}
input: {
(function(a) {
if (true) return;
var b = 42;
})(this);
}
expect: {
this, void 0;
}
}
issue_2084: {
options = {
arrows: true,
collapse_vars: true,
conditionals: true,
ecma: 6,
evaluate: true,
inline: true,
passes: 2,
reduce_vars: true,
sequences: true,
side_effects: true,
unused: true,
}
input: {
var c = 0;
!function() {
!function(c) {
c = 1 + c;
var c = 0;
function f14(a_1) {
if (c = 1 + c, 0 !== 23..toString())
c = 1 + c, a_1 && (a_1[0] = 0);
}
f14();
}(-1);
}();
console.log(c);
}
expect: {
var c = 0;
((c) => {
c = 1 + c,
c = 1 + (c = 0),
0 !== 23..toString() && (c = 1 + c);
})(-1),
console.log(c);
}
expect_stdout: "0"
node_version: ">=4"
}

View File

@@ -6,6 +6,22 @@ await_precedence: {
expect_exact: "async function f1(){await x+y}async function f2(){await(x+y)}"
}
await_precedence_prop: {
input: {
async function f1(){ return (await foo()).bar; }
async function f2(){ return (await foo().bar); }
}
expect_exact: "async function f1(){return(await foo()).bar}async function f2(){return await foo().bar}"
}
await_precedence_call: {
input: {
async function f3(){ return (await foo())(); }
async function f4(){ return await (foo()()); }
}
expect_exact: "async function f3(){return(await foo())()}async function f4(){return await foo()()}"
}
async_function_declaration: {
options = {
side_effects: true,
@@ -252,3 +268,27 @@ async_arrow_wait: {
}
expect_exact: "var a=async(x,y)=>await x(y);"
}
async_arrow_iife: {
input: {
(async () => {
await fetch({});
})();
}
expect_exact: "(async()=>{await fetch({})})();"
}
async_arrow_iife_negate_iife: {
options = {
negate_iife: true,
}
input: {
(async () => {
await fetch();
})();
(() => {
plain();
})();
}
expect_exact: "(async()=>{await fetch()})();(()=>{plain()})();"
}

View File

@@ -2077,10 +2077,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"
}
@@ -2330,3 +2330,73 @@ reassign_const_2: {
}
expect_stdout: true
}
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"
}

View File

@@ -1299,6 +1299,7 @@ issue_2105: {
options = {
collapse_vars: true,
inline: true,
passes: 3,
reduce_vars: true,
side_effects: true,
unused: true,
@@ -1324,7 +1325,7 @@ issue_2105: {
});
}
expect: {
!void function() {
(function() {
var quux = function() {
console.log("PASS");
};
@@ -1334,7 +1335,7 @@ issue_2105: {
quux();
}
};
}().prop();
})().prop();
}
expect_stdout: "PASS"
}
@@ -1409,3 +1410,20 @@ issue_2136_3: {
expect_stdout: "2"
node_version: ">=6"
}
issue_2163: {
options = {
pure_funcs: [ "pure" ],
side_effects: true,
}
input: {
var c;
/*@__PURE__*/f(...a);
pure(b, ...c);
}
expect: {
var c;
a;
b;
}
}

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

@@ -174,3 +174,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

@@ -8,7 +8,7 @@ compress_new_function: {
new Function("aa, bb", 'return aa;');
}
expect: {
Function("n", "r", "return n");
Function("n,r", "return n");
}
}
@@ -27,9 +27,9 @@ compress_new_function_with_destruct: {
new Function("[[aa]], [{bb}]", 'return aa;');
}
expect: {
Function("n", "[r]", "return n");
Function("n", "{bb:b}", "return n");
Function("[[n]]", "[{bb:b}]", "return n");
Function("n,[r]", "return n");
Function("n,{bb:b}", "return n");
Function("[[n]],[{bb:b}]", "return n");
}
}
@@ -49,8 +49,8 @@ compress_new_function_with_destruct_arrows: {
new Function("[[aa]], [{bb}]", 'return aa;');
}
expect: {
Function("aa, [bb]", 'return aa;');
Function("aa, {bb}", 'return aa;');
Function("[[aa]], [{bb}]", 'return aa;');
Function("n,[a]", "return n");
Function("b,{bb:n}", "return b");
Function("[[b]],[{bb:n}]", "return b");
}
}

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

@@ -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 });

View File

@@ -30,4 +30,13 @@ describe("Template string", function() {
assert.throws(exec(tests[i]), fail, tests[i]);
}
});
it("Should process all line terminators as LF", function() {
[
"`a\rb`",
"`a\nb`",
"`a\r\nb`",
].forEach(function(code) {
assert.strictEqual(uglify.parse(code).print_to_string(), "`a\\nb`;");
});
});
});