From 4fa54b075c4507901fd3750e8fc7834cf7e9c809 Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 17 Dec 2020 22:18:47 +0000 Subject: [PATCH] enhance `reduce_vars` (#4392) --- lib/compress.js | 115 +++++++++++++++++++--------------------- test/compress/arrows.js | 85 +++++++++++++++++++++++++++++ test/compress/async.js | 71 +++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 59 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 8fe0ad62..fa59ef5e 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -672,6 +672,53 @@ merge(Compressor.prototype, { return true; } + function reduce_iife(tw, descend, compressor) { + var fn = this; + fn.inlined = false; + var iife = tw.parent(); + var hit = fn instanceof AST_AsyncFunction; + var aborts = false; + fn.walk(new TreeWalker(function(node) { + if (hit) return aborts = true; + if (node instanceof AST_Return) return hit = true; + if (node instanceof AST_Scope && node !== fn) return true; + })); + if (aborts) push(tw); + reset_variables(tw, compressor, fn); + // Virtually turn IIFE parameters into variable definitions: + // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() + // So existing transformation rules can work on them. + var safe = !fn.uses_arguments || tw.has_directive("use strict"); + fn.argnames.forEach(function(arg, i) { + var value = iife.args[i]; + scan_declaration(tw, arg, function() { + var j = fn.argnames.indexOf(arg); + return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); + }, function(node, fixed) { + var d = node.definition(); + if (safe && d.fixed === undefined) { + mark(tw, d); + tw.loop_ids[d.id] = tw.in_loop; + var value = iife.args[i]; + d.fixed = fixed; + d.fixed.assigns = [ arg ]; + } else { + d.fixed = false; + } + }); + }); + if (fn instanceof AST_Arrow && fn.value) { + fn.value.walk(tw); + } else { + walk_body(fn, tw); + } + var safe_ids = tw.safe_ids; + pop(tw); + walk_defuns(tw, fn); + if (!aborts) tw.safe_ids = safe_ids; + return true; + } + def(AST_Assign, function(tw, descend, compressor) { var node = this; var left = node.left; @@ -771,10 +818,14 @@ merge(Compressor.prototype, { tw.find_parent(AST_Scope).may_call_this(); var exp = this.expression; if (is_function(exp)) { + var iife = !exp.name; this.args.forEach(function(arg) { arg.walk(tw); + if (arg instanceof AST_Spread) iife = false; }); + if (iife) exp.reduce_vars = reduce_iife; exp.walk(tw); + if (iife) delete exp.reduce_vars; return true; } else if (exp instanceof AST_SymbolRef) { var def = exp.definition(); @@ -861,62 +912,6 @@ merge(Compressor.prototype, { tw.in_loop = saved_loop; return true; }); - def(AST_Function, function(tw, descend, compressor) { - var fn = this; - fn.inlined = false; - var iife; - if (!fn.name - && (iife = tw.parent()) instanceof AST_Call - && iife.expression === fn - && all(iife.args, function(arg) { - return !(arg instanceof AST_Spread); - })) { - var hit = false; - var aborts = false; - fn.walk(new TreeWalker(function(node) { - if (hit) return aborts = true; - if (node instanceof AST_Return) return hit = true; - if (node instanceof AST_Scope && node !== fn) return true; - })); - if (aborts) push(tw); - reset_variables(tw, compressor, fn); - // Virtually turn IIFE parameters into variable definitions: - // (function(a,b) {...})(c,d) => (function() {var a=c,b=d; ...})() - // So existing transformation rules can work on them. - var safe = !fn.uses_arguments || tw.has_directive("use strict"); - fn.argnames.forEach(function(arg, i) { - var value = iife.args[i]; - scan_declaration(tw, arg, function() { - var j = fn.argnames.indexOf(arg); - return (j < 0 ? value : iife.args[j]) || make_node(AST_Undefined, iife); - }, function(node, fixed) { - var d = node.definition(); - if (safe && d.fixed === undefined) { - mark(tw, d); - tw.loop_ids[d.id] = tw.in_loop; - var value = iife.args[i]; - d.fixed = fixed; - d.fixed.assigns = [ arg ]; - } else { - d.fixed = false; - } - }); - }); - walk_body(fn, tw); - var safe_ids = tw.safe_ids; - pop(tw); - walk_defuns(tw, fn); - if (!aborts) tw.safe_ids = safe_ids; - } else { - push(tw); - reset_variables(tw, compressor, fn); - descend(); - pop(tw); - if (fn.name) mark_escaped(tw, fn.name.definition(), fn, fn.name, fn, 0, 1); - walk_defuns(tw, fn); - } - return true; - }); def(AST_If, function(tw) { this.condition.walk(tw); push(tw); @@ -936,12 +931,14 @@ merge(Compressor.prototype, { return true; }); def(AST_Lambda, function(tw, descend, compressor) { - this.inlined = false; + var fn = this; + fn.inlined = false; push(tw); - reset_variables(tw, compressor, this); + reset_variables(tw, compressor, fn); descend(); pop(tw); - walk_defuns(tw, this); + if (fn.name) mark_escaped(tw, fn.name.definition(), fn, fn.name, fn, 0, 1); + walk_defuns(tw, fn); return true; }); def(AST_Switch, function(tw, descend, compressor) { diff --git a/test/compress/arrows.js b/test/compress/arrows.js index 34af3505..dd71fe9b 100644 --- a/test/compress/arrows.js +++ b/test/compress/arrows.js @@ -337,6 +337,91 @@ trim_body: { node_version: ">=4" } +reduce_iife_1: { + options = { + evaluate: true, + keep_fargs: "strict", + reduce_vars: true, + unused: true, + } + input: { + (a => console.log(a + a))(21); + } + expect: { + (() => console.log(42))(); + } + expect_stdout: "42" + node_version: ">=4" +} + +reduce_iife_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = 21; + (() => console.log(a + a))(); + } + expect: { + (() => console.log(42))(); + } + expect_stdout: "42" + node_version: ">=4" +} + +reduce_iife_3: { + options = { + evaluate: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = "foo"; + (() => { + console.log(a); + console.log(a); + })(); + a = "bar"; + } + expect: { + (() => { + console.log("foo"); + console.log("foo"); + })(); + } + expect_stdout: [ + "foo", + "foo", + ] + node_version: ">=4" +} + +single_use_recursive: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function f() { + return (() => f)(); + } + console.log(typeof f()); + } + expect: { + console.log(typeof function f() { + return (() => f)(); + }()); + } + expect_stdout: "function" + node_version: ">=4" +} + issue_4388: { options = { inline: true, diff --git a/test/compress/async.js b/test/compress/async.js index 8b7f596e..79189c7d 100644 --- a/test/compress/async.js +++ b/test/compress/async.js @@ -329,6 +329,77 @@ property_access_expression: { node_version: ">=8" } +reduce_iife_1: { + options = { + evaluate: true, + keep_fargs: "strict", + reduce_vars: true, + unused: true, + } + input: { + (async function(a) { + console.log(a + a); + })(21); + } + expect: { + (async function() { + console.log(42); + })(); + } + expect_stdout: "42" + node_version: ">=8" +} + +reduce_iife_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = 21; + (async function() { + console.log(a + a); + })(); + } + expect: { + (async function() { + console.log(42); + })(); + } + expect_stdout: "42" + node_version: ">=8" +} + +reduce_iife_3: { + options = { + evaluate: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + var a = "foo"; + (async function() { + console.log(a); + console.log(await a); + })(); + a = "bar"; + } + expect: { + var a = "foo"; + (async function() { + console.log(a); + console.log(await a); + })(); + a = "bar"; + } + expect_stdout: "foo" + node_version: ">=8" +} + issue_4347_1: { options = { evaluate: true, -- 2.34.1