From 8153b7bd8a70ad94666904bd41f12ebd6be684c8 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 7 Mar 2017 15:37:52 +0800 Subject: transform function calls to IIFEs (#1560) - expose function body to call sites for potential optimisations - suppress substitution of variable used within `AST_Defun` --- lib/ast.js | 10 +- lib/compress.js | 51 +++++++--- test/compress/reduce_vars.js | 238 +++++++++++++++++++++++++++++++++++++++++-- test/mocha/cli.js | 2 +- test/mocha/glob.js | 12 +-- 5 files changed, 284 insertions(+), 29 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 1f163304..a2125e70 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -91,7 +91,15 @@ var AST_Token = DEFNODE("Token", "type value line col pos endline endcol endpos }, null); var AST_Node = DEFNODE("Node", "start end", { - clone: function() { + clone: function(deep) { + if (deep) { + var self = this.clone(); + return self.transform(new TreeTransformer(function(node) { + if (node !== self) { + return node.clone(true); + } + })); + } return new this.CTOR(this); }, $documentation: "Base class of all AST nodes", diff --git a/lib/compress.js b/lib/compress.js index 696e2056..8bbbc3f6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -245,7 +245,8 @@ merge(Compressor.prototype, { if (node instanceof AST_SymbolRef) { var d = node.definition(); d.references.push(node); - if (!d.fixed || isModified(node, 0) || !is_safe(d)) { + if (!d.fixed || !is_safe(d) + || is_modified(node, 0, d.fixed instanceof AST_Lambda)) { d.fixed = false; } } @@ -261,6 +262,21 @@ merge(Compressor.prototype, { d.fixed = false; } } + if (node instanceof AST_Defun) { + var d = node.name.definition(); + if (!toplevel && d.global || is_safe(d)) { + d.fixed = false; + } else { + d.fixed = node; + mark_as_safe(d); + } + var save_ids = safe_ids; + safe_ids = []; + push(); + descend(); + safe_ids = save_ids; + return true; + } var iife; if (node instanceof AST_Function && (iife = tw.parent()) instanceof AST_Call @@ -344,13 +360,13 @@ merge(Compressor.prototype, { def.should_replace = undefined; } - function isModified(node, level) { + function is_modified(node, level, func) { var parent = tw.parent(level); if (isLHS(node, parent) - || parent instanceof AST_Call && parent.expression === node) { + || !func && parent instanceof AST_Call && parent.expression === node) { return true; } else if (parent instanceof AST_PropAccess && parent.expression === node) { - return isModified(parent, level + 1); + return !func && is_modified(parent, level + 1); } } }); @@ -1307,14 +1323,7 @@ merge(Compressor.prototype, { def(AST_Statement, function(){ throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start)); }); - // XXX: AST_Accessor and AST_Function both inherit from AST_Scope, - // which itself inherits from AST_Statement; however, they aren't - // really statements. This could bite in other places too. :-( - // Wish JS had multiple inheritance. - def(AST_Accessor, function(){ - throw def; - }); - def(AST_Function, function(){ + def(AST_Lambda, function(){ throw def; }); function ev(node, compressor) { @@ -2590,6 +2599,24 @@ merge(Compressor.prototype, { OPT(AST_Call, function(self, compressor){ var exp = self.expression; + if (compressor.option("reduce_vars") + && exp instanceof AST_SymbolRef) { + var def = exp.definition(); + if (def.fixed instanceof AST_Defun) { + def.fixed = make_node(AST_Function, def.fixed, def.fixed).clone(true); + } + if (def.fixed instanceof AST_Function) { + exp = def.fixed; + if (compressor.option("unused") + && def.references.length == 1 + && compressor.find_parent(AST_Scope) === def.scope) { + if (!compressor.option("keep_fnames")) { + exp.name = null; + } + self.expression = exp; + } + } + } if (compressor.option("unused") && exp instanceof AST_Function && !exp.uses_arguments diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js index 27f77fb5..a373de29 100644 --- a/test/compress/reduce_vars.js +++ b/test/compress/reduce_vars.js @@ -744,12 +744,11 @@ toplevel_on_loops_1: { while (x); } expect: { - function bar() { - console.log("bar:", --x); - } var x = 3; do - bar(); + (function() { + console.log("bar:", --x); + })(); while (x); } } @@ -800,10 +799,9 @@ toplevel_on_loops_2: { while (x); } expect: { - function bar() { + for (;;) (function() { console.log("bar:"); - } - for (;;) bar(); + })(); } } @@ -869,3 +867,229 @@ toplevel_off_loops_3: { for (;x;) bar(); } } + +defun_reference: { + options = { + evaluate: true, + reduce_vars: true, + } + input: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } + expect: { + function f() { + function g() { + x(); + return a; + } + var a = h(); + var b = 2; + return a + b; + function h() { + y(); + return b; + } + } + } +} + +defun_inline_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g(2) + h(); + function g(b) { + return b; + } + function h() { + return h(); + } + } + } + expect: { + function f() { + return function(b) { + return b; + }(2) + h(); + function h() { + return h(); + } + } + } +} + +defun_inline_2: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g(b) { + return b; + } + function h() { + return h(); + } + return g(2) + h(); + } + } + expect: { + function f() { + function h() { + return h(); + } + return function(b) { + return b; + }(2) + h(); + } + } +} + +defun_inline_3: { + options = { + evaluate: true, + passes: 2, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + function f() { + return g(2); + function g(b) { + return b; + } + } + } + expect: { + function f() { + return 2; + } + } +} + +defun_call: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + return g() + h(1) - h(g(), 2, 3); + function g() { + return 4; + } + function h(a) { + return a; + } + } + } + expect: { + function f() { + return 4 + h(1) - h(4); + function h(a) { + return a; + } + } + } +} + +defun_redefine: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + function g() { + return 1; + } + function h() { + return 2; + } + g = function() { + return 3; + }; + return g() + h(); + } + } + expect: { + function f() { + function g() { + return 1; + } + g = function() { + return 3; + }; + return g() + 2; + } + } +} + +func_inline: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f() { + var g = function() { + return 1; + }; + console.log(g() + h()); + var h = function() { + return 2; + }; + } + } + expect: { + function f() { + console.log(1 + h()); + var h = function() { + return 2; + }; + } + } +} + +func_modified: { + options = { + reduce_vars: true, + unused: true, + } + input: { + function f(a) { + function a() { return 1; } + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return a() + b() + c(); + } + } + expect: { + function f(a) { + function b() { return 2; } + function c() { return 3; } + b.inject = []; + c = function() { return 4; }; + return 1 + 2 + c(); + } + } +} diff --git a/test/mocha/cli.js b/test/mocha/cli.js index c07eeee7..e8e07cb5 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -82,7 +82,7 @@ describe("bin/uglifyjs", function () { }); }); it("Should work with --keep-fnames (mangle & compress)", function (done) { - var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c'; + var command = uglifyjscmd + ' test/input/issue-1431/sample.js --keep-fnames -m -c unused=false'; exec(command, function (err, stdout) { if (err) throw err; diff --git a/test/mocha/glob.js b/test/mocha/glob.js index c2fc9464..30313656 100644 --- a/test/mocha/glob.js +++ b/test/mocha/glob.js @@ -3,17 +3,13 @@ var assert = require("assert"); describe("minify() with input file globs", function() { it("minify() with one input file glob string.", function() { - var result = Uglify.minify("test/input/issue-1242/foo.*", { - compress: { collapse_vars: true } - }); + var result = Uglify.minify("test/input/issue-1242/foo.*"); assert.strictEqual(result.code, 'function foo(o){print("Foo:",2*o)}var print=console.log.bind(console);'); }); it("minify() with an array of one input file glob.", function() { var result = Uglify.minify([ "test/input/issue-1242/b*.es5", - ], { - compress: { collapse_vars: true } - }); + ]); assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}'); }); it("minify() with an array of multiple input file globs.", function() { @@ -21,8 +17,8 @@ describe("minify() with input file globs", function() { "test/input/issue-1242/???.es5", "test/input/issue-1242/*.js", ], { - compress: { collapse_vars: true } + compress: { toplevel: true } }); - assert.strictEqual(result.code, 'function bar(n){return 3*n}function baz(n){return n/2}function foo(n){print("Foo:",2*n)}var print=console.log.bind(console);print("qux",bar(3),baz(12)),foo(11);'); + assert.strictEqual(result.code, 'var print=console.log.bind(console);print("qux",function(n){return 3*n}(3),function(n){return n/2}(12)),function(n){print("Foo:",2*n)}(11);'); }); }); -- cgit v1.2.3