diff --git a/README.md b/README.md index 4eb76823..f4aae853 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/lib/compress.js b/lib/compress.js index a41c9fac..1342997f 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -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. diff --git a/test/compress/arrow.js b/test/compress/arrow.js index a0a97e03..9ed08801 100644 --- a/test/compress/arrow.js +++ b/test/compress/arrow.js @@ -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); + } +} diff --git a/test/compress/async.js b/test/compress/async.js index 7bc47012..7ffc1454 100644 --- a/test/compress/async.js +++ b/test/compress/async.js @@ -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: { diff --git a/test/compress/issue-203.js b/test/compress/issue-203.js index 12e23f91..8b6d360a 100644 --- a/test/compress/issue-203.js +++ b/test/compress/issue-203.js @@ -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;'); + } +} diff --git a/test/compress/parameters.js b/test/compress/parameters.js index bcf0c20e..d141fff1 100644 --- a/test/compress/parameters.js +++ b/test/compress/parameters.js @@ -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; }; diff --git a/test/mocha/cli.js b/test/mocha/cli.js index 1e96fc0b..8badf95c 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -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;