aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Lam S.L <alexlamsl@gmail.com>2017-06-06 05:49:53 +0800
committerGitHub <noreply@github.com>2017-06-06 05:49:53 +0800
commit3493a182b2c94e4011b2b12d61376273b985d29a (patch)
tree449238f5af448b90551f66d92ce87a4a2a317103
parent27c5284d3dc0ab131168a73035be7d87ebda30e9 (diff)
downloadtracifyjs-3493a182b2c94e4011b2b12d61376273b985d29a.tar.gz
tracifyjs-3493a182b2c94e4011b2b12d61376273b985d29a.zip
implement function inlining (#2053)
- empty body - single `AST_Return` - single `AST_SimpleStatement` - avoid `/*#__PURE__*/` Miscellaneous - enhance single-use function substitution fixes #281
-rw-r--r--README.md2
-rw-r--r--lib/compress.js88
-rw-r--r--test/compress/collapse_vars.js2
-rw-r--r--test/compress/evaluate.js2
-rw-r--r--test/compress/functions.js4
-rw-r--r--test/compress/issue-1787.js1
-rw-r--r--test/compress/issue-281.js433
-rw-r--r--test/compress/negate-iife.js6
-rw-r--r--test/compress/reduce_vars.js15
-rw-r--r--test/mocha/glob.js2
-rw-r--r--test/mocha/minify.js2
11 files changed, 528 insertions, 29 deletions
diff --git a/README.md b/README.md
index 95d54464..039bc638 100644
--- a/README.md
+++ b/README.md
@@ -616,6 +616,8 @@ If you're using the `X-SourceMap` header instead, you can just omit `sourceMap.u
- `if_return` -- optimizations for if/return and if/continue
+- `inline` -- embed simple functions
+
- `join_vars` -- join consecutive `var` statements
- `cascade` -- small optimization for sequences, transform `x, x` into `x`
diff --git a/lib/compress.js b/lib/compress.js
index effa6b11..969acd61 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -63,6 +63,7 @@ function Compressor(options, false_by_default) {
hoist_vars : false,
ie8 : false,
if_return : !false_by_default,
+ inline : !false_by_default,
join_vars : !false_by_default,
keep_fargs : true,
keep_fnames : false,
@@ -2943,24 +2944,9 @@ 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 (compressor.option("reduce_vars") && exp instanceof AST_SymbolRef) {
var fixed = exp.fixed_value();
- if (fixed instanceof AST_Defun) {
- def.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
- }
- if (fixed instanceof AST_Function) {
- exp = fixed;
- if (compressor.option("unused")
- && def.references.length == 1
- && !(def.scope.uses_arguments
- && def.orig[0] instanceof AST_SymbolFunarg)
- && !def.scope.uses_eval
- && compressor.find_parent(AST_Scope) === exp.parent_scope) {
- self.expression = exp;
- }
- }
+ if (fixed instanceof AST_Function) exp = fixed;
}
if (compressor.option("unused")
&& exp instanceof AST_Function
@@ -3171,13 +3157,61 @@ merge(Compressor.prototype, {
}
}
if (exp instanceof AST_Function) {
- if (exp.body[0] instanceof AST_Return) {
- var value = exp.body[0].value;
+ var stat = exp.body[0];
+ if (compressor.option("inline") && stat instanceof AST_Return) {
+ var value = stat && 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);
}
}
+ if (compressor.option("inline")
+ && !exp.name
+ && exp.body.length == 1
+ && !exp.uses_arguments
+ && !exp.uses_eval
+ && !self.has_pure_annotation(compressor)) {
+ var body;
+ if (stat instanceof AST_Return) {
+ body = stat.value.clone(true);
+ } else if (stat instanceof AST_SimpleStatement) {
+ body = [];
+ merge_sequence(body, stat.body.clone(true));
+ merge_sequence(body, make_node(AST_Undefined, self));
+ body = make_sequence(self, body);
+ }
+ if (body) {
+ var fn = exp.clone();
+ fn.argnames = [];
+ fn.body = [
+ make_node(AST_Var, self, {
+ definitions: exp.argnames.map(function(sym, i) {
+ return make_node(AST_VarDef, sym, {
+ name: sym,
+ value: self.args[i] || 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))
+ }));
+ }
+ fn.body.push(make_node(AST_Return, self, {
+ value: body
+ }));
+ body = fn.transform(compressor).body;
+ if (body.length == 0) return make_node(AST_Undefined, self);
+ if (body.length == 1 && body[0] instanceof AST_Return) {
+ if (!body[0].value) return make_node(AST_Undefined, self);
+ body = best_of(compressor, body[0].value, self);
+ } else {
+ body = self;
+ }
+ if (body !== self) return body;
+ }
+ }
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);
@@ -3836,12 +3870,22 @@ merge(Compressor.prototype, {
return make_node(AST_Infinity, self).optimize(compressor);
}
}
- if (compressor.option("evaluate")
- && compressor.option("reduce_vars")
+ if (compressor.option("reduce_vars")
&& is_lhs(self, compressor.parent()) !== self) {
var d = self.definition();
var fixed = self.fixed_value();
- if (fixed) {
+ if (fixed instanceof AST_Defun) {
+ d.fixed = fixed = make_node(AST_Function, fixed, fixed).clone(true);
+ }
+ if (compressor.option("unused")
+ && fixed instanceof AST_Function
+ && d.references.length == 1
+ && !(d.scope.uses_arguments && d.orig[0] instanceof AST_SymbolFunarg)
+ && !d.scope.uses_eval
+ && compressor.find_parent(AST_Scope) === fixed.parent_scope) {
+ return fixed;
+ }
+ if (compressor.option("evaluate") && fixed) {
if (d.should_replace === undefined) {
var init = fixed.evaluate(compressor);
if (init !== fixed && (compressor.option("unsafe_regexp") || !(init instanceof RegExp))) {
diff --git a/test/compress/collapse_vars.js b/test/compress/collapse_vars.js
index 90d7ac93..158d1b4a 100644
--- a/test/compress/collapse_vars.js
+++ b/test/compress/collapse_vars.js
@@ -1146,7 +1146,7 @@ collapse_vars_constants: {
function f3(x) {
var b = x.prop;
sideeffect1();
- return b + -9;
+ return b + (function() { return -9; })();
}
}
}
diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js
index 99245d0d..27d08d47 100644
--- a/test/compress/evaluate.js
+++ b/test/compress/evaluate.js
@@ -644,6 +644,7 @@ unsafe_prototype_function: {
call_args: {
options = {
evaluate: true,
+ inline: true,
reduce_vars: true,
toplevel: true,
}
@@ -665,6 +666,7 @@ call_args: {
call_args_drop_param: {
options = {
evaluate: true,
+ inline: true,
keep_fargs: false,
reduce_vars: true,
toplevel: true,
diff --git a/test/compress/functions.js b/test/compress/functions.js
index a9ca23f8..180bb11a 100644
--- a/test/compress/functions.js
+++ b/test/compress/functions.js
@@ -21,6 +21,7 @@ iifes_returning_constants_keep_fargs_true: {
join_vars : true,
reduce_vars : true,
cascade : true,
+ inline : true,
}
input: {
(function(){ return -1.23; }());
@@ -56,6 +57,7 @@ iifes_returning_constants_keep_fargs_false: {
join_vars : true,
reduce_vars : true,
cascade : true,
+ inline : true,
}
input: {
(function(){ return -1.23; }());
@@ -82,6 +84,7 @@ issue_485_crashing_1530: {
conditionals: true,
dead_code: true,
evaluate: true,
+ inline: true,
}
input: {
(function(a) {
@@ -154,6 +157,7 @@ function_returning_constant_literal: {
evaluate: true,
cascade: true,
unused: true,
+ inline: true,
}
input: {
function greeter() {
diff --git a/test/compress/issue-1787.js b/test/compress/issue-1787.js
index 02fa0f91..2b5372be 100644
--- a/test/compress/issue-1787.js
+++ b/test/compress/issue-1787.js
@@ -1,6 +1,7 @@
unary_prefix: {
options = {
evaluate: true,
+ inline: true,
reduce_vars: true,
unused: true,
}
diff --git a/test/compress/issue-281.js b/test/compress/issue-281.js
new file mode 100644
index 00000000..6b1b4b40
--- /dev/null
+++ b/test/compress/issue-281.js
@@ -0,0 +1,433 @@
+collapse_vars_constants: {
+ options = {
+ collapse_vars: true,
+ evaluate: true,
+ inline: true,
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ function f1(x) {
+ var a = 4, b = x.prop, c = 5, d = sideeffect1(), e = sideeffect2();
+ return b + (function() { return d - a * e - c; })();
+ }
+ function f2(x) {
+ var a = 4, b = x.prop, c = 5, not_used = sideeffect1(), e = sideeffect2();
+ return b + (function() { return -a * e - c; })();
+ }
+ }
+ expect: {
+ function f1(x) {
+ var b = x.prop, d = sideeffect1(), e = sideeffect2();
+ return b + (d - 4 * e - 5);
+ }
+ function f2(x) {
+ var b = x.prop;
+ sideeffect1();
+ return b + (-4 * sideeffect2() - 5);
+ }
+ }
+}
+
+modified: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ unused: true,
+ }
+ input: {
+ function f5(b) {
+ var a = function() {
+ return b;
+ }();
+ return b++ + a;
+ }
+ console.log(f5(1));
+ }
+ expect: {
+ function f5(b) {
+ var a = b;
+ return b++ + a;
+ }
+ console.log(f5(1));
+ }
+ expect_stdout: "2"
+}
+
+ref_scope: {
+ options = {
+ collapse_vars: true,
+ inline: true,
+ unused: true,
+ }
+ input: {
+ console.log(function() {
+ var a = 1, b = 2, c = 3;
+ var a = c++, b = b /= a;
+ return function() {
+ return a;
+ }() + b;
+ }());
+ }
+ expect: {
+ console.log(function() {
+ var a = 1, b = 2, c = 3;
+ b = b /= a = c++;
+ return a + b;
+ }());
+ }
+ expect_stdout: true
+}
+
+safe_undefined: {
+ options = {
+ conditionals: true,
+ if_return: true,
+ inline: true,
+ unsafe: false,
+ unused: true,
+ }
+ mangle = {}
+ input: {
+ var a, c;
+ console.log(function(undefined) {
+ return function() {
+ if (a)
+ return b;
+ if (c)
+ return d;
+ };
+ }(1)());
+ }
+ expect: {
+ var a, c;
+ console.log(a ? b : c ? d : void 0);
+ }
+ expect_stdout: true
+}
+
+negate_iife_3: {
+ options = {
+ conditionals: true,
+ expression: true,
+ inline: true,
+ negate_iife: true,
+ }
+ input: {
+ (function(){ return t })() ? console.log(true) : console.log(false);
+ }
+ expect: {
+ t ? console.log(true) : console.log(false);
+ }
+}
+
+negate_iife_3_off: {
+ options = {
+ conditionals: true,
+ expression: true,
+ inline: true,
+ negate_iife: false,
+ }
+ input: {
+ (function(){ return t })() ? console.log(true) : console.log(false);
+ }
+ expect: {
+ t ? console.log(true) : console.log(false);
+ }
+}
+
+negate_iife_4: {
+ options = {
+ conditionals: true,
+ expression: true,
+ inline: true,
+ negate_iife: true,
+ sequences: true,
+ }
+ input: {
+ (function(){ return t })() ? console.log(true) : console.log(false);
+ (function(){
+ console.log("something");
+ })();
+ }
+ expect: {
+ t ? console.log(true) : console.log(false), console.log("something"), void 0;
+ }
+}
+
+negate_iife_5: {
+ options = {
+ conditionals: true,
+ expression: true,
+ inline: true,
+ negate_iife: true,
+ sequences: true,
+ }
+ input: {
+ if ((function(){ return t })()) {
+ foo(true);
+ } else {
+ bar(false);
+ }
+ (function(){
+ console.log("something");
+ })();
+ }
+ expect: {
+ t ? foo(true) : bar(false), console.log("something"), void 0;
+ }
+}
+
+negate_iife_5_off: {
+ options = {
+ conditionals: true,
+ expression: true,
+ inline: true,
+ negate_iife: false,
+ sequences: true,
+ };
+ input: {
+ if ((function(){ return t })()) {
+ foo(true);
+ } else {
+ bar(false);
+ }
+ (function(){
+ console.log("something");
+ })();
+ }
+ expect: {
+ t ? foo(true) : bar(false), console.log("something"), void 0;
+ }
+}
+
+issue_1254_negate_iife_true: {
+ options = {
+ expression: true,
+ inline: true,
+ negate_iife: true,
+ }
+ input: {
+ (function() {
+ return function() {
+ console.log('test')
+ };
+ })()();
+ }
+ expect_exact: 'console.log("test"),void 0;'
+ expect_stdout: true
+}
+
+issue_1254_negate_iife_nested: {
+ options = {
+ expression: true,
+ inline: true,
+ negate_iife: true,
+ }
+ input: {
+ (function() {
+ return function() {
+ console.log('test')
+ };
+ })()()()()();
+ }
+ expect_exact: '(console.log("test"),void 0)()()();'
+}
+
+negate_iife_issue_1073: {
+ options = {
+ conditionals: true,
+ evaluate: true,
+ inline: true,
+ negate_iife: true,
+ reduce_vars: true,
+ sequences: true,
+ unused: true,
+ };
+ input: {
+ new (function(a) {
+ return function Foo() {
+ this.x = a;
+ console.log(this);
+ };
+ }(7))();
+ }
+ expect: {
+ new function() {
+ this.x = 7,
+ console.log(this);
+ }();
+ }
+ expect_stdout: true
+}
+
+issue_1288_side_effects: {
+ options = {
+ conditionals: true,
+ evaluate: true,
+ inline: true,
+ negate_iife: true,
+ reduce_vars: true,
+ side_effects: true,
+ unused: true,
+ };
+ input: {
+ if (w) ;
+ else {
+ (function f() {})();
+ }
+ if (!x) {
+ (function() {
+ x = {};
+ })();
+ }
+ if (y)
+ (function() {})();
+ else
+ (function(z) {
+ return z;
+ })(0);
+ }
+ expect: {
+ w;
+ x || (x = {});
+ y;
+ }
+}
+
+inner_var_for_in_1: {
+ options = {
+ evaluate: true,
+ inline: true,
+ reduce_vars: true,
+ }
+ input: {
+ function f() {
+ var a = 1, b = 2;
+ for (b in (function() {
+ return x(a, b, c);
+ })()) {
+ var c = 3, d = 4;
+ x(a, b, c, d);
+ }
+ x(a, b, c, d);
+ }
+ }
+ expect: {
+ function f() {
+ var a = 1, b = 2;
+ for (b in x(1, b, c)) {
+ var c = 3, d = 4;
+ x(1, b, c, d);
+ }
+ x(1, b, c, d);
+ }
+ }
+}
+
+issue_1595_3: {
+ options = {
+ evaluate: true,
+ inline: true,
+ passes: 2,
+ reduce_vars: true,
+ unused: true,
+ }
+ input: {
+ (function f(a) {
+ return g(a + 1);
+ })(2);
+ }
+ expect: {
+ g(3);
+ }
+}
+
+issue_1758: {
+ options = {
+ inline: true,
+ sequences: true,
+ side_effects: true,
+ }
+ input: {
+ console.log(function(c) {
+ var undefined = 42;
+ return function() {
+ c--;
+ c--, c.toString();
+ return;
+ }();
+ }());
+ }
+ expect: {
+ console.log(function(c) {
+ var undefined = 42;
+ return c--, c--, void c.toString();
+ }());
+ }
+ expect_stdout: "undefined"
+}
+wrap_iife: {
+ options = {
+ inline: true,
+ negate_iife: false,
+ }
+ beautify = {
+ wrap_iife: true,
+ }
+ input: {
+ (function() {
+ return function() {
+ console.log('test')
+ };
+ })()();
+ }
+ expect_exact: 'console.log("test"),void 0;'
+}
+
+wrap_iife_in_expression: {
+ options = {
+ inline: true,
+ negate_iife: false,
+ }
+ beautify = {
+ wrap_iife: true,
+ }
+ input: {
+ foo = (function () {
+ return bar();
+ })();
+ }
+ expect_exact: 'foo=bar();'
+}
+
+wrap_iife_in_return_call: {
+ options = {
+ inline: true,
+ negate_iife: false,
+ }
+ beautify = {
+ wrap_iife: true,
+ }
+ input: {
+ (function() {
+ return (function() {
+ console.log('test')
+ })();
+ })()();
+ }
+ expect_exact: '(console.log("test"),void 0)();'
+}
+
+pure_annotation: {
+ options = {
+ inline: true,
+ side_effects: true,
+ }
+ input: {
+ /*@__PURE__*/(function() {
+ console.log("hello");
+ }());
+ }
+ expect_exact: ""
+}
diff --git a/test/compress/negate-iife.js b/test/compress/negate-iife.js
index 514a15c7..66d24270 100644
--- a/test/compress/negate-iife.js
+++ b/test/compress/negate-iife.js
@@ -22,7 +22,8 @@ negate_iife_1_off: {
negate_iife_2: {
options = {
- negate_iife: true
+ inline: true,
+ negate_iife: true,
};
input: {
(function(){ return {} })().x = 10;
@@ -32,6 +33,7 @@ negate_iife_2: {
negate_iife_2_side_effects: {
options = {
+ inline: true,
negate_iife: true,
side_effects: true,
}
@@ -58,6 +60,7 @@ negate_iife_3_evaluate: {
options = {
conditionals: true,
evaluate: true,
+ inline: true,
negate_iife: true,
}
input: {
@@ -100,6 +103,7 @@ negate_iife_3_off_evaluate: {
options = {
conditionals: true,
evaluate: true,
+ inline: true,
negate_iife: false,
}
input: {
diff --git a/test/compress/reduce_vars.js b/test/compress/reduce_vars.js
index 078de82d..cef29832 100644
--- a/test/compress/reduce_vars.js
+++ b/test/compress/reduce_vars.js
@@ -2,6 +2,7 @@ reduce_vars: {
options = {
conditionals : true,
evaluate : true,
+ inline : true,
global_defs : {
C : 0
},
@@ -1032,6 +1033,7 @@ defun_inline_2: {
defun_inline_3: {
options = {
evaluate: true,
+ inline: true,
passes: 2,
reduce_vars: true,
side_effects: true,
@@ -1054,6 +1056,7 @@ defun_inline_3: {
defun_call: {
options = {
+ inline: true,
reduce_vars: true,
unused: true,
}
@@ -1080,6 +1083,7 @@ defun_call: {
defun_redefine: {
options = {
+ inline: true,
reduce_vars: true,
unused: true,
}
@@ -1112,6 +1116,7 @@ defun_redefine: {
func_inline: {
options = {
+ inline: true,
reduce_vars: true,
unused: true,
}
@@ -1138,6 +1143,7 @@ func_inline: {
func_modified: {
options = {
+ inline: true,
reduce_vars: true,
unused: true,
}
@@ -1340,10 +1346,9 @@ iife_func_side_effects: {
console.log("z");
}
(function(a, b, c) {
- function y() {
+ return function() {
console.log("FAIL");
- }
- return y + b();
+ } + b();
})(x(), function() {
return y();
}, z());
@@ -1716,6 +1721,7 @@ redefine_arguments_1: {
redefine_arguments_2: {
options = {
evaluate: true,
+ inline: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
@@ -1752,6 +1758,7 @@ redefine_arguments_2: {
redefine_arguments_3: {
options = {
evaluate: true,
+ inline: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
@@ -1828,6 +1835,7 @@ redefine_farg_1: {
redefine_farg_2: {
options = {
evaluate: true,
+ inline: true,
keep_fargs: false,
reduce_vars: true,
side_effects: true,
@@ -1864,6 +1872,7 @@ redefine_farg_2: {
redefine_farg_3: {
options = {
evaluate: true,
+ inline: true,
keep_fargs: false,
passes: 3,
reduce_vars: true,
diff --git a/test/mocha/glob.js b/test/mocha/glob.js
index 56d3f82a..8567ecb3 100644
--- a/test/mocha/glob.js
+++ b/test/mocha/glob.js
@@ -31,7 +31,7 @@ describe("bin/uglifyjs with input file globs", function() {
exec(command, function(err, stdout) {
if (err) throw err;
- assert.strictEqual(stdout, 'var print=console.log.bind(console),a=function(n){return 3*n}(3),b=function(n){return n/2}(12);print("qux",a,b),function(n){print("Foo:",2*n)}(11);\n');
+ assert.strictEqual(stdout, 'var print=console.log.bind(console);print("qux",9,6),print("Foo:",2*11);\n');
done();
});
});
diff --git a/test/mocha/minify.js b/test/mocha/minify.js
index 77b798ec..519b725c 100644
--- a/test/mocha/minify.js
+++ b/test/mocha/minify.js
@@ -106,7 +106,7 @@ describe("minify", function() {
content: "inline"
}
});
- assert.strictEqual(result.code, "var bar=function(){function foo(bar){return bar}return foo}();");
+ assert.strictEqual(result.code, "var bar=function(){return function(bar){return bar}}();");
assert.strictEqual(warnings.length, 1);
assert.strictEqual(warnings[0], "inline source map not found");
} finally {