From e5cbd3e0f36afbbd3055c151d5f77561002dc50c Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Tue, 22 Nov 2022 17:57:16 +0000 Subject: [PATCH] enhance `join_vars` (#5739) --- lib/compress.js | 91 ++++++++++++++++++++++--------- test/benchmark.js | 1 + test/compress/let.js | 124 ++++++++++++++++++++++++++++++++++++++++++ test/release/buble.sh | 5 ++ 4 files changed, 195 insertions(+), 26 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 73701fec..a3a930f2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -487,7 +487,7 @@ Compressor.prototype.compress = function(node) { return all(def.orig, function(sym) { if (sym instanceof AST_SymbolConst || sym instanceof AST_SymbolLet) { if (sym instanceof AST_SymbolImport) return true; - return compressor && can_varify(compressor, sym); + return compressor && safe_from_tdz(compressor, sym); } return !(keep_lambda && sym instanceof AST_SymbolLambda); }); @@ -525,10 +525,11 @@ Compressor.prototype.compress = function(node) { function reset_def(tw, compressor, def) { def.assignments = 0; def.bool_return = 0; - def.drop_return = 0; def.cross_loop = false; def.direct_access = false; + def.drop_return = 0; def.escaped = []; + def.first_decl = null; def.fixed = !def.const_redefs && !def.scope.pinned() && !compressor.exposed(def) @@ -951,6 +952,7 @@ Compressor.prototype.compress = function(node) { function visit(node, fixed) { var d = node.definition(); + if (!d.first_decl && d.references.length == 0) d.first_decl = node; if (fixed && safe && d.fixed === undefined) { mark(tw, d); tw.loop_ids[d.id] = tw.in_loop; @@ -1371,10 +1373,18 @@ Compressor.prototype.compress = function(node) { return true; }); def(AST_SymbolCatch, function() { - this.definition().fixed = false; + var d = this.definition(); + if (!d.first_decl && d.references.length == 0) d.first_decl = this; + d.fixed = false; + }); + def(AST_SymbolDeclaration, function() { + var d = this.definition(); + if (!d.first_decl && d.references.length == 0) d.first_decl = this; }); def(AST_SymbolImport, function() { - this.definition().fixed = false; + var d = this.definition(); + d.first_decl = this; + d.fixed = false; }); def(AST_SymbolRef, function(tw, descend, compressor) { var ref = this; @@ -1558,6 +1568,8 @@ Compressor.prototype.compress = function(node) { walk_defn(); } else if (tw.parent() instanceof AST_Let) { walk_defn(); + } else { + node.name.walk(tw); } return true; @@ -1566,6 +1578,7 @@ Compressor.prototype.compress = function(node) { return node.value || make_node(AST_Undefined, node); }, function(name, fixed) { var d = name.definition(); + if (!d.first_decl && d.references.length == 0) d.first_decl = name; if (fixed && safe_to_assign(tw, d, true)) { mark(tw, d); tw.loop_ids[d.id] = tw.in_loop; @@ -4249,7 +4262,7 @@ Compressor.prototype.compress = function(node) { } function join_consecutive_vars(statements) { - var changed = false, defs; + var changed = false, defs, prev_defs; for (var i = 0, j = -1; i < statements.length; i++) { var stat = statements[i]; var prev = statements[j]; @@ -4257,6 +4270,14 @@ Compressor.prototype.compress = function(node) { if (prev && prev.TYPE == stat.TYPE) { prev.definitions = prev.definitions.concat(stat.definitions); changed = true; + } else if (stat && prev instanceof AST_Let && stat.can_letify(compressor)) { + prev.definitions = prev.definitions.concat(to_let(stat).definitions); + changed = true; + } else if (prev && stat instanceof AST_Let && prev.can_letify(compressor)) { + defs = prev_defs; + statements[j] = prev = to_let(prev); + prev.definitions = prev.definitions.concat(stat.definitions); + changed = true; } else if (defs && defs.TYPE == stat.TYPE && declarations_only(stat)) { defs.definitions = defs.definitions.concat(stat.definitions); changed = true; @@ -4271,6 +4292,7 @@ Compressor.prototype.compress = function(node) { } else { j++; } + prev_defs = defs; statements[j] = defs = stat; } else { statements[++j] = stat; @@ -4288,6 +4310,7 @@ Compressor.prototype.compress = function(node) { prev.definitions = prev.definitions.concat(stat.init.definitions); } stat = stat.clone(); + prev_defs = defs; defs = stat.init = prev; statements[j] = merge_defns(stat); changed = true; @@ -4297,6 +4320,7 @@ Compressor.prototype.compress = function(node) { stat.init = null; changed = true; } else if (stat.init instanceof AST_Var) { + prev_defs = defs; defs = stat.init; exprs = merge_assigns(prev, stat.init); if (exprs) { @@ -9365,7 +9389,7 @@ Compressor.prototype.compress = function(node) { } }, true)) { self.init = to_var(self.init, self.resolve()); - } else if (can_letify(self.init, compressor, 1)) { + } else if (self.init.can_letify(compressor, true)) { self.init = to_let(self.init); } } @@ -10213,19 +10237,34 @@ Compressor.prototype.compress = function(node) { }); } - function can_letify(stat, compressor, assigned) { - if (!(stat instanceof AST_Const)) return false; - if (!compressor.option("module") && all(stat.definitions, function(defn) { - return defn.name instanceof AST_SymbolConst; - })) return false; - return all(stat.definitions, function(defn) { - return !defn.name.match_symbol(function(node) { - if (node instanceof AST_SymbolDeclaration) return node.definition().assignments != assigned; - }, true); + (function(def) { + def(AST_Node, return_false); + def(AST_Const, function(compressor, assigned) { + assigned = assigned ? 1 : 0; + var defns = this.definitions; + if (!compressor.option("module") && all(defns, function(defn) { + return defn.name instanceof AST_SymbolConst; + })) return false; + return all(defns, function(defn) { + return !defn.name.match_symbol(function(node) { + if (node instanceof AST_SymbolDeclaration) return node.definition().assignments != assigned; + }, true); + }); }); - } + def(AST_Var, function(compressor) { + return all(this.definitions, function(defn) { + return !defn.name.match_symbol(function(node) { + if (!(node instanceof AST_SymbolDeclaration)) return false; + if (node.definition().first_decl !== node) return true; + return !safe_from_tdz(compressor, node); + }, true); + }); + }); + })(function(node, func) { + node.DEFMETHOD("can_letify", func); + }); - function can_varify(compressor, sym) { + function safe_from_tdz(compressor, sym) { var def = sym.definition(); return (def.fixed || def.fixed === 0) && is_safe_lexical(def) @@ -10233,25 +10272,25 @@ Compressor.prototype.compress = function(node) { && !may_overlap(compressor, def); } - function varify(self, compressor) { - if (all(self.definitions, function(defn) { + AST_Definitions.DEFMETHOD("can_varify", function(compressor) { + return all(this.definitions, function(defn) { return !defn.name.match_symbol(function(node) { - if (node instanceof AST_SymbolDeclaration) return !can_varify(compressor, node); + if (node instanceof AST_SymbolDeclaration) return !safe_from_tdz(compressor, node); }, true); - })) return to_var(self, compressor.find_parent(AST_Scope)); - } + }); + }); OPT(AST_Const, function(self, compressor) { if (!compressor.option("varify")) return self; - var decl = varify(self, compressor); - if (decl) return decl; - if (can_letify(self, compressor, 0)) return to_let(self); + if (self.can_varify(compressor)) return to_var(self, compressor.find_parent(AST_Scope)); + if (self.can_letify(compressor)) return to_let(self); return self; }); OPT(AST_Let, function(self, compressor) { if (!compressor.option("varify")) return self; - return varify(self, compressor) || self; + if (self.can_varify(compressor)) return to_var(self, compressor.find_parent(AST_Scope)); + return self; }); function trim_optional_chain(node, compressor) { diff --git a/test/benchmark.js b/test/benchmark.js index 53d9ee2f..da29558c 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -3,6 +3,7 @@ "use strict"; +require("../tools/tty"); var createHash = require("crypto").createHash; var fetch = require("./fetch"); var spawn = require("child_process").spawn; diff --git a/test/compress/let.js b/test/compress/let.js index c070f6b7..69d0d370 100644 --- a/test/compress/let.js +++ b/test/compress/let.js @@ -1130,6 +1130,130 @@ default_init: { node_version: ">=4" } +join_let_var_1: { + options = { + join_vars: true, + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + var a = "foo"; + let b = "bar"; + for (var c of [ a, b ]) + console.log(c); + } + expect: { + "use strict"; + let a = "foo", b = "bar"; + for (var c of [ a, b ]) + console.log(c); + } + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +join_let_var_2: { + options = { + join_vars: true, + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + let a = "foo"; + var b = "bar"; + for (let c of [ a, b ]) + console.log(c); + } + expect: { + "use strict"; + let a = "foo", b = "bar"; + for (let c of [ a, b ]) + console.log(c); + } + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +keep_let_var_1: { + options = { + join_vars: true, + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + var a = "foo"; + let b = "bar"; + for (var c of [ a, b ]) + console.log(c); + function f() { + return a; + } + console.log(f(f)); + } + expect: { + "use strict"; + var a = "foo", c; + let b = "bar"; + for (c of [ a, b ]) + console.log(c); + function f() { + return a; + } + console.log(f(f)); + } + expect_stdout: [ + "foo", + "bar", + "foo", + ] + node_version: ">=4" +} + +keep_let_var_2: { + options = { + join_vars: true, + reduce_vars: true, + toplevel: true, + } + input: { + "use strict"; + let a = "foo"; + var b = "bar"; + for (let c of [ a, b ]) + console.log(c); + function f() { + return b; + } + console.log(f(f)); + } + expect: { + "use strict"; + let a = "foo"; + var b = "bar"; + for (let c of [ a, b ]) + console.log(c); + function f() { + return b; + } + console.log(f(f)); + } + expect_stdout: [ + "foo", + "bar", + "bar", + ] + node_version: ">=4" +} + issue_4191: { options = { functions: true, diff --git a/test/release/buble.sh b/test/release/buble.sh index 56599e3d..9208020f 100755 --- a/test/release/buble.sh +++ b/test/release/buble.sh @@ -41,6 +41,11 @@ rm -rf tmp/buble \ @@ -309 +309 @@ export default class BlockStatement extends Node { - let cont = false; // TODO implement proper continue... + let cont = !declarations; // TODO implement proper continue... +--- a/src/program/types/VariableDeclaration.js ++++ b/src/program/types/VariableDeclaration.js +@@ -38 +38 @@ export default class VariableDeclaration extends Node { +- code.remove(c, declarator.id.start); ++ code.remove(c, declarator.id.start, lastDeclaratorIsPattern); EOF ERR=$?; if [ "$ERR" != "0" ]; then echo "Error: $ERR"; exit $ERR; fi minify_in_situ "src" \