add new arrows compress option (#2154)

Convert ES5 style anonymous function expressions
to arrow functions if permissible by language semantics.

Note: `arrows` requires that the `ecma` compress option
is set to `6` or greater.

fixes #2150
This commit is contained in:
Alex Lam S.L
2017-06-24 14:45:24 +08:00
committed by GitHub
parent 7b95b63ca1
commit d1f085bce7
7 changed files with 140 additions and 10 deletions

View File

@@ -590,6 +590,10 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `evaluate` -- attempt to evaluate constant expressions
- `arrows` (default `true`) -- convert ES5 style anonymous function expressions
to arrow functions if permissible by language semantics.
Note: `arrows` requires that the `ecma` compress option is set to `6` or greater.
- `booleans` -- various optimizations for boolean context, for example `!!a
? b : c → a ? b : c`

View File

@@ -48,6 +48,7 @@ function Compressor(options, false_by_default) {
return new Compressor(options, false_by_default);
TreeTransformer.call(this, this.before, this.after);
this.options = defaults(options, {
arrows : !false_by_default,
booleans : !false_by_default,
cascade : !false_by_default,
collapse_vars : !false_by_default,
@@ -2047,7 +2048,7 @@ merge(Compressor.prototype, {
});
OPT(AST_Block, function(self, compressor){
if (!(self.body instanceof AST_Node)) tighten_body(self.body, compressor);
tighten_body(self.body, compressor);
return self;
});
@@ -3273,11 +3274,12 @@ merge(Compressor.prototype, {
var fun;
ast.walk(new TreeWalker(function(node) {
if (fun) return true;
if (node instanceof AST_Lambda) {
if (node instanceof AST_Function) {
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()
@@ -4499,13 +4501,37 @@ merge(Compressor.prototype, {
});
OPT(AST_Arrow, function(self, compressor){
if (self.body.length === 1 && self.body[0] instanceof AST_Return) {
if (!(self.body instanceof AST_Node)) tighten_body(self.body, compressor);
if (compressor.option("arrows")
&& self.body.length == 1
&& self.body[0] instanceof AST_Return) {
var value = self.body[0].value;
self.body = value ? value : [];
}
return self;
});
OPT(AST_Function, function(self, compressor){
tighten_body(self.body, compressor);
if (compressor.option("arrows")
&& compressor.option("ecma") >= 6
&& !self.name
&& !self.is_generator
&& !self.uses_arguments
&& !self.uses_eval) {
var has_special_symbol = false;
self.walk(new TreeWalker(function(node) {
if (has_special_symbol) return true;
if (node instanceof AST_Symbol && !node.definition()) {
has_special_symbol = true;
return true;
}
}));
if (!has_special_symbol) return make_node(AST_Arrow, self, self).optimize(compressor);
}
return self;
});
OPT(AST_Class, function(self, compressor){
// HACK to avoid compress failure.
// AST_Class is not really an AST_Scope/AST_Block as it lacks a body.

View File

@@ -210,3 +210,73 @@ no_leading_parentheses: {
}
expect_exact: "(x,y)=>x(y);async(x,y)=>await x(y);"
}
async_identifiers: {
options = {
arrows: true,
ecma: 6,
}
input: {
var async = function(x){ console.log("async", x); };
var await = function(x){ console.log("await", x); };
async(1);
await(2);
}
expect: {
var async = x => { console.log("async", x); };
var await = x => { console.log("await", x); };
async(1);
await(2);
}
expect_stdout: [
"async 1",
"await 2",
]
node_version: ">=4"
}
async_function_expression: {
options = {
arrows: true,
ecma: 6,
evaluate: true,
side_effects: true,
}
input: {
var named = async function foo() {
await bar(1 + 0) + (2 + 0);
}
var anon = async function() {
await (1 + 0) + bar(2 + 0);
}
}
expect: {
var named = async function foo() {
await bar(1);
};
var anon = async () => {
await 1, bar(2);
};
}
}
issue_27: {
options = {
arrows: true,
collapse_vars: true,
ecma: 6,
unused: true,
}
input: {
(function(jQuery) {
var $;
$ = jQuery;
$("body").addClass("foo");
})(jQuery);
}
expect: {
(jQuery => {
jQuery("body").addClass("foo");
})(jQuery);
}
}

View File

@@ -167,14 +167,14 @@ async_inline: {
async_identifiers: {
input: {
let async = function(x){ console.log("async", x); };
let await = function(x){ console.log("await", x); };
var async = function(x){ console.log("async", x); };
var await = function(x){ console.log("await", x); };
async(1);
await(2);
}
expect: {
let async = function(x){ console.log("async", x); };
let await = function(x){ console.log("await", x); };
var async = function(x){ console.log("async", x); };
var await = function(x){ console.log("await", x); };
async(1);
await(2);
}
@@ -182,7 +182,6 @@ async_identifiers: {
"async 1",
"await 2",
]
node_version: ">=8"
}
async_shorthand_property: {

View File

@@ -32,3 +32,25 @@ compress_new_function_with_destruct: {
Function("[[a]]", "[{bb:b}]", 'return a');
}
}
compress_new_function_with_destruct_arrows: {
options = {
arrows: true,
unsafe: true,
unsafe_Func: true,
ecma: 6
}
beautify = {
ecma: 6
}
input: {
new Function("aa, [bb]", 'return aa;');
new Function("aa, {bb}", 'return aa;');
new Function("[[aa]], [{bb}]", 'return aa;');
}
expect: {
Function("aa, [bb]", 'return aa;');
Function("aa, {bb}", 'return aa;');
Function("[[aa]], [{bb}]", 'return aa;');
}
}

View File

@@ -1,4 +1,7 @@
arrow_functions: {
options = {
arrows: true,
}
input: {
(a) => b; // 1 args
(a, b) => c; // n args
@@ -13,6 +16,9 @@ arrow_functions: {
}
arrow_return: {
options = {
arrows: true,
}
input: {
() => {};
() => { return; };

View File

@@ -1,6 +1,7 @@
var assert = require("assert");
var exec = require("child_process").exec;
var readFileSync = require("fs").readFileSync;
var semver = require("semver");
function read(path) {
return readFileSync(path, "utf8");
@@ -9,9 +10,11 @@ function read(path) {
describe("bin/uglifyjs", function () {
var uglifyjscmd = '"' + process.argv[0] + '" bin/uglifyjs';
it("should produce a functional build when using --self", function (done) {
this.timeout(30000);
this.timeout(60000);
var command = uglifyjscmd + ' --self -cm --wrap WrappedUglifyJS';
var command = uglifyjscmd + ' --self -mc ecma=';
command += semver.satisfies(process.version, ">=4") ? "6" : "5";
command += ',passes=3,keep_fargs=false,unsafe --wrap WrappedUglifyJS';
exec(command, function (err, stdout) {
if (err) throw err;