From f8dd879873a8b860673f4877fb25043f57cb5f66 Mon Sep 17 00:00:00 2001 From: Geraint Date: Tue, 28 Jun 2016 15:52:49 +0100 Subject: [PATCH] "reorder_funs" - sort function declarations by complexity This reduces the size after gzip. Complexity is (for now) measured simply by counting AST nodes. --- README.md | 4 ++++ lib/compress.js | 33 ++++++++++++++++++++++++++++----- test/compress/reorder-funs.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 test/compress/reorder-funs.js diff --git a/README.md b/README.md index 2ab9de62..b43da125 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,10 @@ to set `true`; it's effectively a shortcut for `foo=true`). - `hoist_funs` -- hoist function declarations +- `reorder_funs` -- rearrange function declarations in order of increasing + complexity. This has no effect on size before gzip, but often reduces size + after gzip. + - `hoist_vars` (default: false) -- hoist `var` declarations (this is `false` by default because it seems to increase the size of the output in general) diff --git a/lib/compress.js b/lib/compress.js index 4152bd06..aeb325d2 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -61,6 +61,7 @@ function Compressor(options, false_by_default) { loops : !false_by_default, unused : !false_by_default, hoist_funs : !false_by_default, + reorder_funs : !false_by_default, keep_fargs : true, keep_fnames : false, hoist_vars : false, @@ -108,14 +109,12 @@ merge(Compressor.prototype, { }, before: function(node, descend, in_list) { if (node._squeezed) return node; - var was_scope = false; - if (node instanceof AST_Scope) { - node = node.hoist_declarations(this); - was_scope = true; - } + var was_scope = node instanceof AST_Scope; descend(node, this); node = node.optimize(this); if (was_scope && node instanceof AST_Scope) { + node = node.ast_complexity(this); + node = node.hoist_declarations(this); node.drop_unused(this); descend(node, this); } @@ -152,10 +151,26 @@ merge(Compressor.prototype, { if (!(node instanceof AST_Directive || node instanceof AST_Constant)) { node._squeezed = false; node._optimized = false; + node._complexity = 0; } })); }); + AST_Node.DEFMETHOD("ast_complexity", function(){ + var self = this; + var complexity = 0; + self.walk(new TreeWalker(function(node){ + if (node._complexity) { + complexity += node._complexity; + return true; + } else { + complexity++; + } + })); + self._complexity = complexity; + return self; + }); + function make_node(ctor, orig, props) { if (!props) props = {}; if (orig) { @@ -1574,6 +1589,14 @@ merge(Compressor.prototype, { } ); self = self.transform(tt); + if (compressor.option('reorder_funs')) { + // Moving simpler functions earlier tends to give better gzip compression + hoisted.sort(function (a, b) { + var aComplexity = a._complexity; + var bComplexity = b._complexity; + return (aComplexity - bComplexity) || (a.start.pos - b.start.pos); + }); + } if (vars_found > 0) { // collect only vars which don't show up in self's arguments list var defs = []; diff --git a/test/compress/reorder-funs.js b/test/compress/reorder-funs.js new file mode 100644 index 00000000..86afc8b9 --- /dev/null +++ b/test/compress/reorder-funs.js @@ -0,0 +1,33 @@ +reorder_functions_after_optimize: { + options = { + collapse_vars:true, sequences:true, properties:true, dead_code:true, conditionals:true, + comparisons:true, evaluate:true, booleans:true, loops:true, unused:true, hoist_funs:true, + reorder_funs:true, keep_fargs:true, if_return:true, join_vars:true, cascade:true, side_effects:true + } + input: { + function longFun(x, y) { + return [x, y].map(function (x) { + return x*x; + }); + } + function medFun(x) { + return 15*x; + } + function shortFun() { + return 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; + } + } + expect: { + function shortFun() { + return 55; + } + function medFun(x) { + return 15*x; + } + function longFun(x, y) { + return [x, y].map(function (x) { + return x*x; + }); + } + } +} \ No newline at end of file