few more optimizations:
- do multiple passes in tighten_body if it was changed - transform if (foo) return x; return y; ==> return foo?x:y - don't optimize !0 as true (use best_of after evaluation of constant expr) With hoist_vars off we now beat UglifyJS v1 on jQuery-1.8.1
This commit is contained in:
241
lib/compress.js
241
lib/compress.js
@@ -67,6 +67,7 @@ function Compressor(options, false_by_default) {
|
|||||||
unused_func : !false_by_default,
|
unused_func : !false_by_default,
|
||||||
hoist_funs : !false_by_default,
|
hoist_funs : !false_by_default,
|
||||||
hoist_vars : !false_by_default,
|
hoist_vars : !false_by_default,
|
||||||
|
if_return : !false_by_default,
|
||||||
|
|
||||||
warnings : true
|
warnings : true
|
||||||
});
|
});
|
||||||
@@ -93,6 +94,7 @@ function Compressor(options, false_by_default) {
|
|||||||
push_node : function(node) { stack.push(node) },
|
push_node : function(node) { stack.push(node) },
|
||||||
pop_node : function() { return stack.pop() },
|
pop_node : function() { return stack.pop() },
|
||||||
stack : function() { return stack },
|
stack : function() { return stack },
|
||||||
|
self : function() { return stack[stack.length - 1] },
|
||||||
parent : function(n) {
|
parent : function(n) {
|
||||||
return stack[stack.length - 2 - (n || 0)];
|
return stack[stack.length - 2 - (n || 0)];
|
||||||
},
|
},
|
||||||
@@ -122,9 +124,9 @@ function Compressor(options, false_by_default) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function SQUEEZE(nodetype, squeeze) {
|
function SQUEEZE(nodetype, squeeze) {
|
||||||
nodetype.DEFMETHOD("squeeze", function(compressor, block, index){
|
nodetype.DEFMETHOD("squeeze", function(compressor){
|
||||||
compressor.push_node(this);
|
compressor.push_node(this);
|
||||||
var new_node = squeeze(this, compressor, block, index);
|
var new_node = squeeze(this, compressor);
|
||||||
compressor.pop_node();
|
compressor.pop_node();
|
||||||
return new_node !== undefined ? new_node : this;
|
return new_node !== undefined ? new_node : this;
|
||||||
});
|
});
|
||||||
@@ -156,94 +158,148 @@ function Compressor(options, false_by_default) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function tighten_body(statements, compressor) {
|
function tighten_body(statements, compressor) {
|
||||||
|
var CHANGED;
|
||||||
statements = do_list(statements, compressor, true);
|
statements = do_list(statements, compressor, true);
|
||||||
if (compressor.option("dead_code")) {
|
do {
|
||||||
statements = eliminate_dead_code(statements, compressor);
|
CHANGED = false;
|
||||||
}
|
if (compressor.option("dead_code")) {
|
||||||
if (compressor.option("sequences")) {
|
statements = eliminate_dead_code(statements, compressor);
|
||||||
statements = sequencesize(statements, compressor);
|
}
|
||||||
}
|
if (compressor.option("sequences")) {
|
||||||
|
statements = sequencesize(statements, compressor);
|
||||||
|
}
|
||||||
|
if (compressor.option("if_return")) {
|
||||||
|
statements = handle_if_return(statements, compressor);
|
||||||
|
}
|
||||||
|
} while (CHANGED);
|
||||||
return statements;
|
return statements;
|
||||||
|
|
||||||
|
function handle_if_return(statements, compressor) {
|
||||||
|
var in_lambda = compressor.self() instanceof AST_Lambda;
|
||||||
|
var last = statements.length - 1;
|
||||||
|
return MAP(statements, function(stat, i){
|
||||||
|
if (stat instanceof AST_If
|
||||||
|
&& stat.body instanceof AST_Return
|
||||||
|
&& !stat.body.value
|
||||||
|
&& !stat.alternative
|
||||||
|
&& in_lambda) {
|
||||||
|
CHANGED = true;
|
||||||
|
if (i < last) {
|
||||||
|
var rest = statements.slice(i + 1);
|
||||||
|
var cond = stat.condition;
|
||||||
|
while (rest[0] instanceof AST_If
|
||||||
|
&& rest[0].body instanceof AST_Return
|
||||||
|
&& !rest[0].alternative) {
|
||||||
|
cond = make_node(AST_Binary, rest[0], {
|
||||||
|
operator: "||",
|
||||||
|
left: cond,
|
||||||
|
right: rest[0].condition
|
||||||
|
});
|
||||||
|
rest.shift();
|
||||||
|
}
|
||||||
|
return MAP.last(make_node(AST_If, stat, {
|
||||||
|
condition: cond.negate(compressor),
|
||||||
|
body: make_node(AST_BlockStatement, stat, {
|
||||||
|
body: rest
|
||||||
|
}).optimize(compressor)
|
||||||
|
}).optimize(compressor));
|
||||||
|
} else {
|
||||||
|
return make_node(AST_SimpleStatement, stat, {
|
||||||
|
body: stat.condition
|
||||||
|
}).optimize(compressor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (stat instanceof AST_If
|
||||||
|
&& stat.body instanceof AST_Return
|
||||||
|
&& stat.body.value
|
||||||
|
&& !stat.alternative
|
||||||
|
&& i < last
|
||||||
|
&& statements[i + 1] instanceof AST_Return
|
||||||
|
&& statements[i + 1].value) {
|
||||||
|
CHANGED = true;
|
||||||
|
return MAP.last(make_node(AST_If, stat, {
|
||||||
|
condition: stat.condition,
|
||||||
|
body: stat.body,
|
||||||
|
alternative: statements[i + 1]
|
||||||
|
}).optimize(compressor));
|
||||||
|
}
|
||||||
|
return stat;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function eliminate_dead_code(statements, compressor) {
|
||||||
|
var has_quit = false;
|
||||||
|
return statements.reduce(function(a, stat){
|
||||||
|
if (has_quit) {
|
||||||
|
extract_declarations_from_unreachable_code(compressor, stat, a);
|
||||||
|
} else {
|
||||||
|
a.push(stat);
|
||||||
|
if (stat instanceof AST_Jump) {
|
||||||
|
has_quit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
// XXX: this is destructive -- it modifies tree nodes.
|
||||||
|
function sequencesize(statements) {
|
||||||
|
var prev = null, last = statements.length - 1;
|
||||||
|
if (last) statements = statements.reduce(function(a, cur, i){
|
||||||
|
if (prev instanceof AST_SimpleStatement
|
||||||
|
&& cur instanceof AST_SimpleStatement) {
|
||||||
|
CHANGED = true;
|
||||||
|
var seq = make_node(AST_Seq, prev, {
|
||||||
|
first: prev.body,
|
||||||
|
second: cur.body
|
||||||
|
});
|
||||||
|
prev.body = seq;
|
||||||
|
}
|
||||||
|
else if (i == last
|
||||||
|
&& cur instanceof AST_Exit && cur.value
|
||||||
|
&& a.length > 0
|
||||||
|
&& prev instanceof AST_SimpleStatement) {
|
||||||
|
CHANGED = true;
|
||||||
|
var seq = make_node(AST_Seq, prev, {
|
||||||
|
first: prev.body,
|
||||||
|
second: cur.value
|
||||||
|
});
|
||||||
|
cur.value = seq;
|
||||||
|
a.pop();
|
||||||
|
a.push(cur);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
a.push(cur);
|
||||||
|
prev = cur;
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}, []);
|
||||||
|
return statements;
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function extract_declarations_from_unreachable_code(compressor, stat, target) {
|
function extract_declarations_from_unreachable_code(compressor, stat, target) {
|
||||||
warn_dead_code(stat);
|
warn_dead_code(stat);
|
||||||
stat.walk(new TreeWalker(function(node){
|
stat.walk(new TreeWalker(function(node){
|
||||||
if (node instanceof AST_Definitions || node instanceof AST_Defun) {
|
if (node instanceof AST_Definitions) {
|
||||||
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
compressor.warn("Declarations in unreachable code! [{line},{col}]", node.start);
|
||||||
if (node instanceof AST_Definitions) {
|
node = node.clone();
|
||||||
node = node.clone();
|
node.remove_initializers();
|
||||||
node.remove_initializers();
|
target.push(node);
|
||||||
target.push(node);
|
return true;
|
||||||
}
|
}
|
||||||
else if (node instanceof AST_Defun) {
|
if (node instanceof AST_Defun) {
|
||||||
target.push(node);
|
target.push(node);
|
||||||
}
|
return true;
|
||||||
return true; // no point to descend
|
|
||||||
}
|
}
|
||||||
if (node instanceof AST_Scope) {
|
if (node instanceof AST_Scope) {
|
||||||
// also don't descend any other nested scopes
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
function eliminate_dead_code(statements, compressor) {
|
|
||||||
var has_quit = false;
|
|
||||||
return statements.reduce(function(a, stat){
|
|
||||||
if (has_quit) {
|
|
||||||
if (stat instanceof AST_Defun) {
|
|
||||||
a.push(stat);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
extract_declarations_from_unreachable_code(compressor, stat, a);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
a.push(stat);
|
|
||||||
if (stat instanceof AST_Jump) {
|
|
||||||
has_quit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
// XXX: this is destructive -- it modifies tree nodes.
|
|
||||||
function sequencesize(statements) {
|
|
||||||
var prev = null, last = statements.length - 1;
|
|
||||||
if (last) statements = statements.reduce(function(a, cur, i){
|
|
||||||
if (prev instanceof AST_SimpleStatement
|
|
||||||
&& cur instanceof AST_SimpleStatement) {
|
|
||||||
var seq = make_node(AST_Seq, prev, {
|
|
||||||
first: prev.body,
|
|
||||||
second: cur.body
|
|
||||||
});
|
|
||||||
prev.body = seq;
|
|
||||||
}
|
|
||||||
else if (i == last
|
|
||||||
&& cur instanceof AST_Exit && cur.value
|
|
||||||
&& a.length > 0
|
|
||||||
&& prev instanceof AST_SimpleStatement) {
|
|
||||||
// it only makes sense to do this transformation
|
|
||||||
// if the AST gets to a single statement.
|
|
||||||
var seq = make_node(AST_Seq, prev, {
|
|
||||||
first: prev.body,
|
|
||||||
second: cur.value
|
|
||||||
});
|
|
||||||
cur.value = seq;
|
|
||||||
a.pop();
|
|
||||||
a.push(cur);
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
a.push(cur);
|
|
||||||
prev = cur;
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
}, []);
|
|
||||||
return statements;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -----[ boolean/negation helpers ]----- */
|
/* -----[ boolean/negation helpers ]----- */
|
||||||
|
|
||||||
// methods to determine whether an expression has a boolean result type
|
// methods to determine whether an expression has a boolean result type
|
||||||
@@ -337,7 +393,7 @@ function Compressor(options, false_by_default) {
|
|||||||
type: typeof val
|
type: typeof val
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return [ ast, val ];
|
return [ best_of(ast, this), val ];
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
if (ex !== def) throw ex;
|
if (ex !== def) throw ex;
|
||||||
return [ this ];
|
return [ this ];
|
||||||
@@ -432,8 +488,7 @@ function Compressor(options, false_by_default) {
|
|||||||
var self = this.clone();
|
var self = this.clone();
|
||||||
self.consequent = self.consequent.negate(compressor);
|
self.consequent = self.consequent.negate(compressor);
|
||||||
self.alternative = self.alternative.negate(compressor);
|
self.alternative = self.alternative.negate(compressor);
|
||||||
//return best_of(basic_negation(this), self);
|
return best_of(basic_negation(this), self);
|
||||||
return self;
|
|
||||||
});
|
});
|
||||||
def(AST_Binary, function(compressor){
|
def(AST_Binary, function(compressor){
|
||||||
var self = this.clone(), op = this.operator;
|
var self = this.clone(), op = this.operator;
|
||||||
@@ -723,16 +778,16 @@ function Compressor(options, false_by_default) {
|
|||||||
return self;
|
return self;
|
||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_If, function(self, compressor, block, index){
|
SQUEEZE(AST_If, function(self, compressor){
|
||||||
self = self.clone();
|
self = self.clone();
|
||||||
self.condition = self.condition.squeeze(compressor);
|
self.condition = self.condition.squeeze(compressor);
|
||||||
self.body = self.body.squeeze(compressor);
|
self.body = self.body.squeeze(compressor);
|
||||||
if (self.alternative)
|
if (self.alternative)
|
||||||
self.alternative = self.alternative.squeeze(compressor);
|
self.alternative = self.alternative.squeeze(compressor);
|
||||||
return self.optimize(compressor, block, index);
|
return self.optimize(compressor);
|
||||||
});
|
});
|
||||||
|
|
||||||
AST_If.DEFMETHOD("optimize", function(compressor, block, index){
|
AST_If.DEFMETHOD("optimize", function(compressor){
|
||||||
var self = this;
|
var self = this;
|
||||||
if (!compressor.option("conditionals")) return self;
|
if (!compressor.option("conditionals")) return self;
|
||||||
// if condition can be statically determined, warn and drop
|
// if condition can be statically determined, warn and drop
|
||||||
@@ -827,29 +882,6 @@ function Compressor(options, false_by_default) {
|
|||||||
}).optimize(compressor)
|
}).optimize(compressor)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (self.body instanceof AST_Return
|
|
||||||
&& !self.body.value
|
|
||||||
&& !self.alternative
|
|
||||||
&& index < block.length - 1) {
|
|
||||||
if (compressor.parent() instanceof AST_Lambda) {
|
|
||||||
var rest = tighten_body(block.slice(index + 1), compressor);
|
|
||||||
var cond = self.condition;
|
|
||||||
while (rest[0] instanceof AST_If && rest[0].body instanceof AST_Return && !rest[0].alternative) {
|
|
||||||
cond = make_node(AST_Binary, rest[0], {
|
|
||||||
operator: "||",
|
|
||||||
left: cond,
|
|
||||||
right: rest[0].condition
|
|
||||||
});
|
|
||||||
rest.shift();
|
|
||||||
}
|
|
||||||
return MAP.last(make_node(AST_If, self, {
|
|
||||||
condition: cond.negate(compressor),
|
|
||||||
body: make_node(AST_BlockStatement, block[index + 1], {
|
|
||||||
body: rest
|
|
||||||
}).optimize(compressor)
|
|
||||||
}).optimize(compressor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (self.body instanceof AST_If
|
if (self.body instanceof AST_If
|
||||||
&& !self.body.alternative
|
&& !self.body.alternative
|
||||||
&& !self.alternative) {
|
&& !self.alternative) {
|
||||||
@@ -1081,7 +1113,6 @@ function Compressor(options, false_by_default) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
SQUEEZE(AST_UnaryPrefix, function(self, compressor){
|
||||||
// need to determine the context before cloning the node
|
|
||||||
self = self.clone();
|
self = self.clone();
|
||||||
self.expression = self.expression.squeeze(compressor);
|
self.expression = self.expression.squeeze(compressor);
|
||||||
return self.optimize(compressor);
|
return self.optimize(compressor);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ ifs_4: {
|
|||||||
|
|
||||||
ifs_5: {
|
ifs_5: {
|
||||||
options = {
|
options = {
|
||||||
conditionals: true
|
if_return: true
|
||||||
};
|
};
|
||||||
input: {
|
input: {
|
||||||
function f() {
|
function f() {
|
||||||
|
|||||||
Reference in New Issue
Block a user