From 13062a47586f88402a604ba2781dbbd26be4295e Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Thu, 22 Feb 2018 21:46:53 +0800 Subject: [PATCH 1/2] implement `group_voids` fixes #2585 --- lib/minify.js | 1 + lib/scope.js | 71 ++++++++++++- test/compress/group_voids.js | 198 +++++++++++++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 test/compress/group_voids.js diff --git a/lib/minify.js b/lib/minify.js index a68cbf3a..ce0954c4 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -75,6 +75,7 @@ function minify(files, options) { options.mangle = defaults(options.mangle, { cache: options.nameCache && (options.nameCache.vars || {}), eval: false, + group_voids: false, ie8: false, keep_fnames: false, properties: false, diff --git a/lib/scope.js b/lib/scope.js index 6c883c66..f9880a4f 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -400,6 +400,7 @@ AST_Symbol.DEFMETHOD("global", function(){ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options) { options = defaults(options, { eval : false, + group_voids : false, ie8 : false, keep_fnames : false, reserved : [], @@ -457,6 +458,18 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ this.walk(tw); to_mangle.forEach(function(def){ def.mangle(options) }); + if (options.group_voids) { + base54.reset(); + base54.sort(); + if (options.toplevel) this.group_voids(options); + else this.walk(new TreeWalker(function(node) { + if (node instanceof AST_Scope && !(node instanceof AST_Toplevel)) { + node.group_voids(options); + return true; + } + })); + } + function collect(symbol) { if (!member(symbol.name, options.reserved)) { to_mangle.push(symbol); @@ -464,11 +477,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ } }); -AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { +AST_Scope.DEFMETHOD("find_colliding_names", function(options, all) { var cache = options.cache && options.cache.props; var avoid = Object.create(null); options.reserved.forEach(to_avoid); - this.globals.each(add_def); + if (this.globals) this.globals.each(add_def); this.walk(new TreeWalker(function(node) { if (node instanceof AST_Scope) node.variables.each(add_def); if (node instanceof AST_SymbolCatch) add_def(node.definition()); @@ -480,9 +493,9 @@ AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { } function add_def(def) { - var name = def.name; + var name = def.mangled_name || def.name; if (def.global && cache && cache.has(name)) name = cache.get(name); - else if (!def.unmangleable(options)) return; + else if (!all && !def.unmangleable(options)) return; to_avoid(name); } }); @@ -522,6 +535,56 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) { } }); +AST_Scope.DEFMETHOD("group_voids", function(options) { + var avoid = this.find_colliding_names(options, true); + var cname = 0; + var name; + do { + name = base54(cname++); + } while (avoid[name] || !is_identifier(name)); + var count = 0; + this.transform(new TreeTransformer(function(node) { + if (node instanceof AST_Undefined + || node instanceof AST_UnaryPrefix + && node.operator == "void" + && node.expression.is_constant()) { + count++; + return new AST_SymbolRef({ + name: name, + start: node.start, + end: node.end + }); + } + })); + if (count) { + for (var i = 0, len = this.body.length; i < len; i++) { + var stat = this.body[i]; + if (stat instanceof AST_Var) { + stat.definitions.push(make_var_def(stat)); + return; + } + } + this.body.push(new AST_Var({ + definitions: [ make_var_def(this) ], + start: this.start, + end: this.end + })); + } + + function make_var_def(node) { + return new AST_VarDef({ + name: new AST_SymbolVar({ + name: name, + start: node.start, + end: node.end + }), + value: null, + start: node.start, + end: node.end + }); + } +}); + AST_Node.DEFMETHOD("tail_node", return_this); AST_Sequence.DEFMETHOD("tail_node", function() { return this.expressions[this.expressions.length - 1]; diff --git a/test/compress/group_voids.js b/test/compress/group_voids.js new file mode 100644 index 00000000..2d960079 --- /dev/null +++ b/test/compress/group_voids.js @@ -0,0 +1,198 @@ +group_voids: { + options = { + } + mangle = { + group_voids: true, + toplevel: false, + } + input: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var a = 1; + console.log(void 0); + } + function f2(undefined) { + var a = 2; + console.log(void 0); + } + function f3() { + var undefined = 3; + console.log(void 0); + } + function f4() { + console.log(void 0); + for (var a = 4;;); + var b = 4; + function f5() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f6() { + try { + var a = 6; + console.log(void 0); + } catch (e) { + console.log(void 0); + } + } + } + expect: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var o = 1, a; + console.log(a); + } + function f2(o) { + var n = 2, a; + console.log(a); + } + function f3() { + var o = 3, a; + console.log(a); + } + function f4() { + console.log(a); + for(var o = 4;;); + var n = 4, a; + function v() { + var o = 5; + var n = 5; + console.log(a); + } + } + function f6() { + try { + var o = 6; + console.log(a); + } catch (o) { + console.log(a); + } + var a; + } + } +} + +group_voids_toplevel: { + options = { + } + mangle = { + group_voids: true, + toplevel: true, + } + input: { + var a = 0; + x = void 0; + if (void 0 === b) + c = void 0; + function f1() { + var a = 1; + console.log(void 0); + } + function f2(undefined) { + var a = 2; + console.log(void 0); + } + function f3() { + var undefined = 3; + console.log(void 0); + } + function f4() { + console.log(void 0); + for (var a = 4;;); + var b = 4; + function f5() { + var c = 5; + var d = 5; + console.log(void 0); + } + } + function f6() { + try { + var a = 6; + console.log(void 0); + } catch (e) { + console.log(void 0); + } + } + } + expect: { + var o = 0, a; + x = a; + if (a === b) + c = a; + function n() { + var o = 1; + console.log(a); + } + function v(o) { + var n = 2; + console.log(a); + } + function i() { + var o = 3; + console.log(a); + } + function l() { + console.log(a); + for(var o = 4;;); + var n = 4; + function v() { + var o = 5; + var n = 5; + console.log(a); + } + } + function r() { + try { + var o = 6; + console.log(a); + } catch (o) { + console.log(a); + } + } + } +} + +group_voids_catch: { + options = { + } + mangle = { + group_voids: true, + } + input: { + f(); + function f() { + var a = 1; + console.log(void 0); + try { + throw "FAIL"; + } catch (undefined) { + console.log(void 0); + } + } + } + expect: { + f(); + function f() { + var o = 1, a; + console.log(a); + try { + throw "FAIL"; + } catch (o) { + console.log(a); + } + } + } + expect_stdout: [ + "undefined", + "undefined", + ] +} From 423d0146be1e17b11cb8270ab9d32c8550b5bd00 Mon Sep 17 00:00:00 2001 From: alexlamsl Date: Fri, 23 Feb 2018 00:28:59 +0800 Subject: [PATCH 2/2] no `base54.reset()` slightly less improved gzip results --- lib/scope.js | 104 ++++++++++++++++++++--------------- test/compress/group_voids.js | 50 ++++++++--------- 2 files changed, 83 insertions(+), 71 deletions(-) diff --git a/lib/scope.js b/lib/scope.js index f9880a4f..09807d57 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -458,17 +458,14 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ this.walk(tw); to_mangle.forEach(function(def){ def.mangle(options) }); - if (options.group_voids) { - base54.reset(); - base54.sort(); - if (options.toplevel) this.group_voids(options); - else this.walk(new TreeWalker(function(node) { - if (node instanceof AST_Scope && !(node instanceof AST_Toplevel)) { - node.group_voids(options); - return true; - } - })); - } + if (!options.group_voids) return; + if (options.toplevel) this.group_voids(options); + else this.walk(new TreeWalker(function(node) { + if (node instanceof AST_Scope && !(node instanceof AST_Toplevel)) { + node.group_voids(options); + return true; + } + })); function collect(symbol) { if (!member(symbol.name, options.reserved)) { @@ -477,11 +474,11 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ } }); -AST_Scope.DEFMETHOD("find_colliding_names", function(options, all) { +AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) { var cache = options.cache && options.cache.props; var avoid = Object.create(null); options.reserved.forEach(to_avoid); - if (this.globals) this.globals.each(add_def); + this.globals.each(add_def); this.walk(new TreeWalker(function(node) { if (node instanceof AST_Scope) node.variables.each(add_def); if (node instanceof AST_SymbolCatch) add_def(node.definition()); @@ -493,9 +490,9 @@ AST_Scope.DEFMETHOD("find_colliding_names", function(options, all) { } function add_def(def) { - var name = def.mangled_name || def.name; + var name = def.name; if (def.global && cache && cache.has(name)) name = cache.get(name); - else if (!all && !def.unmangleable(options)) return; + else if (!def.unmangleable(options)) return; to_avoid(name); } }); @@ -536,39 +533,56 @@ AST_Toplevel.DEFMETHOD("expand_names", function(options) { }); AST_Scope.DEFMETHOD("group_voids", function(options) { - var avoid = this.find_colliding_names(options, true); - var cname = 0; - var name; - do { - name = base54(cname++); - } while (avoid[name] || !is_identifier(name)); - var count = 0; - this.transform(new TreeTransformer(function(node) { - if (node instanceof AST_Undefined - || node instanceof AST_UnaryPrefix - && node.operator == "void" - && node.expression.is_constant()) { - count++; - return new AST_SymbolRef({ - name: name, - start: node.start, - end: node.end - }); + var self = this, enclosed = []; + var scopes = [], count = 0; + self.walk(new TreeWalker(function(node, descend) { + if (node instanceof AST_Scope && node !== self) { + scopes.unshift(node); + descend(); + if (scopes[0] === node) scopes.shift(); + return true; } - })); - if (count) { - for (var i = 0, len = this.body.length; i < len; i++) { - var stat = this.body[i]; - if (stat instanceof AST_Var) { - stat.definitions.push(make_var_def(stat)); - return; + if (count > 0 && scopes.length == 0) return; + if (is_undefined(node)) { + count++; + var scope; + while (scope = scopes.shift()) { + scope.enclosed.forEach(function(def) { + push_uniq(enclosed, def); + }); } } - this.body.push(new AST_Var({ - definitions: [ make_var_def(this) ], - start: this.start, - end: this.end - })); + })); + if (count < 1) return; + var save = self.enclosed; + self.enclosed = enclosed; + var name = next_mangled(self, options); + self.enclosed = save; + this.transform(new TreeTransformer(function(node) { + if (is_undefined(node)) return new AST_SymbolRef({ + name: name, + start: node.start, + end: node.end + }); + })); + for (var i = 0, len = this.body.length; i < len; i++) { + var stat = this.body[i]; + if (stat instanceof AST_Var) { + stat.definitions.push(make_var_def(stat)); + return; + } + } + this.body.push(new AST_Var({ + definitions: [ make_var_def(this) ], + start: this.start, + end: this.end + })); + + function is_undefined(node) { + return node instanceof AST_Undefined + || node instanceof AST_UnaryPrefix + && node.operator == "void" + && node.expression.is_constant(); } function make_var_def(node) { diff --git a/test/compress/group_voids.js b/test/compress/group_voids.js index 2d960079..1eedcfa9 100644 --- a/test/compress/group_voids.js +++ b/test/compress/group_voids.js @@ -26,13 +26,13 @@ group_voids: { console.log(void 0); for (var a = 4;;); var b = 4; - function f5() { + function g() { var c = 5; var d = 5; console.log(void 0); } } - function f6() { + function f5() { try { var a = 6; console.log(void 0); @@ -47,35 +47,35 @@ group_voids: { if (void 0 === b) c = void 0; function f1() { - var o = 1, a; - console.log(a); + var o = 1, n; + console.log(n); } function f2(o) { - var n = 2, a; - console.log(a); + var n = 2, v; + console.log(v); } function f3() { - var o = 3, a; - console.log(a); + var o = 3, n; + console.log(n); } function f4() { - console.log(a); + console.log(i); for(var o = 4;;); - var n = 4, a; + var n = 4, i; function v() { var o = 5; var n = 5; - console.log(a); + console.log(i); } } - function f6() { + function f5() { try { var o = 6; - console.log(a); + console.log(n); } catch (o) { - console.log(a); + console.log(n); } - var a; + var n; } } } @@ -108,13 +108,13 @@ group_voids_toplevel: { console.log(void 0); for (var a = 4;;); var b = 4; - function f5() { + function g() { var c = 5; var d = 5; console.log(void 0); } } - function f6() { + function f5() { try { var a = 6; console.log(void 0); @@ -168,8 +168,7 @@ group_voids_catch: { group_voids: true, } input: { - f(); - function f() { + (function() { var a = 1; console.log(void 0); try { @@ -177,19 +176,18 @@ group_voids_catch: { } catch (undefined) { console.log(void 0); } - } + })(); } expect: { - f(); - function f() { - var o = 1, a; - console.log(a); + (function() { + var o = 1, c; + console.log(c); try { throw "FAIL"; } catch (o) { - console.log(a); + console.log(c); } - } + })(); } expect_stdout: [ "undefined",