@@ -60,6 +60,7 @@ function Compressor(options, false_by_default) {
|
|||||||
expression : false,
|
expression : false,
|
||||||
global_defs : {},
|
global_defs : {},
|
||||||
hoist_funs : !false_by_default,
|
hoist_funs : !false_by_default,
|
||||||
|
hoist_props : false,
|
||||||
hoist_vars : false,
|
hoist_vars : false,
|
||||||
ie8 : false,
|
ie8 : false,
|
||||||
if_return : !false_by_default,
|
if_return : !false_by_default,
|
||||||
@@ -190,6 +191,7 @@ merge(Compressor.prototype, {
|
|||||||
if (node._squeezed) return node;
|
if (node._squeezed) return node;
|
||||||
var was_scope = false;
|
var was_scope = false;
|
||||||
if (node instanceof AST_Scope) {
|
if (node instanceof AST_Scope) {
|
||||||
|
node = node.hoist_properties(this);
|
||||||
node = node.hoist_declarations(this);
|
node = node.hoist_declarations(this);
|
||||||
was_scope = true;
|
was_scope = true;
|
||||||
}
|
}
|
||||||
@@ -547,6 +549,7 @@ merge(Compressor.prototype, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reset_def(def) {
|
function reset_def(def) {
|
||||||
|
def.direct_access = false;
|
||||||
def.escaped = false;
|
def.escaped = false;
|
||||||
if (def.scope.uses_eval) {
|
if (def.scope.uses_eval) {
|
||||||
def.fixed = false;
|
def.fixed = false;
|
||||||
@@ -604,15 +607,19 @@ merge(Compressor.prototype, {
|
|||||||
|| parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
|
|| parent instanceof AST_Return && node === parent.value && node.scope !== d.scope
|
||||||
|| parent instanceof AST_VarDef && node === parent.value) {
|
|| parent instanceof AST_VarDef && node === parent.value) {
|
||||||
d.escaped = true;
|
d.escaped = true;
|
||||||
|
return;
|
||||||
} else if (parent instanceof AST_Array || parent instanceof AST_Object) {
|
} else if (parent instanceof AST_Array || parent instanceof AST_Object) {
|
||||||
mark_escaped(d, parent, parent, level + 1);
|
mark_escaped(d, parent, parent, level + 1);
|
||||||
} else if (parent instanceof AST_PropAccess && node === parent.expression) {
|
} else if (parent instanceof AST_PropAccess && node === parent.expression) {
|
||||||
mark_escaped(d, parent, read_property(value, parent.property), level + 1);
|
value = read_property(value, parent.property);
|
||||||
|
mark_escaped(d, parent, value, level + 1);
|
||||||
|
if (value) return;
|
||||||
}
|
}
|
||||||
|
if (level == 0) d.direct_access = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AST_SymbolRef.DEFMETHOD("fixed_value", function() {
|
AST_Symbol.DEFMETHOD("fixed_value", function() {
|
||||||
var fixed = this.definition().fixed;
|
var fixed = this.definition().fixed;
|
||||||
if (!fixed || fixed instanceof AST_Node) return fixed;
|
if (!fixed || fixed instanceof AST_Node) return fixed;
|
||||||
return fixed();
|
return fixed();
|
||||||
@@ -2478,11 +2485,11 @@ merge(Compressor.prototype, {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
switch (body.length) {
|
switch (body.length) {
|
||||||
case 0:
|
case 0:
|
||||||
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
|
return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
|
||||||
case 1:
|
case 1:
|
||||||
return body[0];
|
return body[0];
|
||||||
default:
|
default:
|
||||||
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
|
return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, {
|
||||||
body: body
|
body: body
|
||||||
});
|
});
|
||||||
@@ -2678,6 +2685,71 @@ merge(Compressor.prototype, {
|
|||||||
return self;
|
return self;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AST_Scope.DEFMETHOD("hoist_properties", function(compressor){
|
||||||
|
var self = this;
|
||||||
|
if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
|
||||||
|
var defs_by_id = Object.create(null);
|
||||||
|
var var_names = Object.create(null);
|
||||||
|
self.enclosed.forEach(function(def) {
|
||||||
|
var_names[def.name] = true;
|
||||||
|
});
|
||||||
|
self.variables.each(function(def, name) {
|
||||||
|
var_names[name] = true;
|
||||||
|
});
|
||||||
|
return self.transform(new TreeTransformer(function(node) {
|
||||||
|
if (node instanceof AST_VarDef) {
|
||||||
|
var sym = node.name, def, value;
|
||||||
|
if (sym.scope === self
|
||||||
|
&& !(def = sym.definition()).escaped
|
||||||
|
&& !def.single_use
|
||||||
|
&& !def.direct_access
|
||||||
|
&& (value = sym.fixed_value()) === node.value
|
||||||
|
&& value instanceof AST_Object) {
|
||||||
|
var defs = new Dictionary();
|
||||||
|
var assignments = [];
|
||||||
|
value.properties.forEach(function(prop) {
|
||||||
|
assignments.push(make_node(AST_VarDef, node, {
|
||||||
|
name: make_sym(prop.key),
|
||||||
|
value: prop.value
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
defs_by_id[def.id] = defs;
|
||||||
|
return MAP.splice(assignments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node instanceof AST_PropAccess && node.expression instanceof AST_SymbolRef) {
|
||||||
|
var defs = defs_by_id[node.expression.definition().id];
|
||||||
|
if (defs) {
|
||||||
|
var key = node.property;
|
||||||
|
if (key instanceof AST_Node) key = key.getValue();
|
||||||
|
var def = defs.get(key);
|
||||||
|
var sym = make_node(AST_SymbolRef, node, {
|
||||||
|
name: def.name,
|
||||||
|
scope: node.expression.scope,
|
||||||
|
thedef: def
|
||||||
|
});
|
||||||
|
sym.reference({});
|
||||||
|
return sym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_sym(key) {
|
||||||
|
var prefix = sym.name + "_" + key.toString().replace(/[^a-z_$]+/ig, "_");
|
||||||
|
var name = prefix;
|
||||||
|
for (var i = 0; var_names[name]; i++) name = prefix + "$" + i;
|
||||||
|
var new_var = make_node(sym.CTOR, sym, {
|
||||||
|
name: name,
|
||||||
|
scope: self
|
||||||
|
});
|
||||||
|
var def = self.def_variable(new_var);
|
||||||
|
defs.set(key, def);
|
||||||
|
self.enclosed.push(def);
|
||||||
|
var_names[name] = true;
|
||||||
|
return new_var;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
// drop_side_effect_free()
|
// drop_side_effect_free()
|
||||||
// remove side-effect-free parts which only affects return value
|
// remove side-effect-free parts which only affects return value
|
||||||
(function(def){
|
(function(def){
|
||||||
|
|||||||
371
test/compress/hoist_props.js
Normal file
371
test/compress/hoist_props.js
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
issue_2377_1: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
inline: true,
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj = {
|
||||||
|
foo: 1,
|
||||||
|
bar: 2,
|
||||||
|
square: function(x) {
|
||||||
|
return x * x;
|
||||||
|
},
|
||||||
|
cube: function(x) {
|
||||||
|
return x * x * x;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(obj.foo, obj.cube(3));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var obj_foo = 1, obj_cube = function(x) {
|
||||||
|
return x * x * x;
|
||||||
|
};
|
||||||
|
console.log(obj_foo, obj_cube(3));
|
||||||
|
}
|
||||||
|
expect_stdout: "1 27"
|
||||||
|
}
|
||||||
|
|
||||||
|
issue_2377_2: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
inline: true,
|
||||||
|
hoist_props: true,
|
||||||
|
passes: 2,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj = {
|
||||||
|
foo: 1,
|
||||||
|
bar: 2,
|
||||||
|
square: function(x) {
|
||||||
|
return x * x;
|
||||||
|
},
|
||||||
|
cube: function(x) {
|
||||||
|
return x * x * x;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(obj.foo, obj.cube(3));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(1, function(x) {
|
||||||
|
return x * x * x;
|
||||||
|
}(3));
|
||||||
|
}
|
||||||
|
expect_stdout: "1 27"
|
||||||
|
}
|
||||||
|
|
||||||
|
issue_2377_3: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
inline: true,
|
||||||
|
hoist_props: true,
|
||||||
|
passes: 3,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj = {
|
||||||
|
foo: 1,
|
||||||
|
bar: 2,
|
||||||
|
square: function(x) {
|
||||||
|
return x * x;
|
||||||
|
},
|
||||||
|
cube: function(x) {
|
||||||
|
return x * x * x;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(obj.foo, obj.cube(3));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(1, 27);
|
||||||
|
}
|
||||||
|
expect_stdout: "1 27"
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_access_1: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var a = 0;
|
||||||
|
var obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
};
|
||||||
|
for (var k in obj) a++;
|
||||||
|
console.log(a, obj.a);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var a = 0;
|
||||||
|
var obj = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
};
|
||||||
|
for (var k in obj) a++;
|
||||||
|
console.log(a, obj.a);
|
||||||
|
}
|
||||||
|
expect_stdout: "2 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_access_2: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = { a: 1 };
|
||||||
|
var f = function(k) {
|
||||||
|
if (o[k]) return "PASS";
|
||||||
|
};
|
||||||
|
console.log(f("a"));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o = { a: 1 };
|
||||||
|
console.log(function(k) {
|
||||||
|
if (o[k]) return "PASS";
|
||||||
|
}("a"));
|
||||||
|
}
|
||||||
|
expect_stdout: "PASS"
|
||||||
|
}
|
||||||
|
|
||||||
|
direct_access_3: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = { a: 1 };
|
||||||
|
o.b;
|
||||||
|
console.log(o.a);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o = { a: 1 };
|
||||||
|
o.b;
|
||||||
|
console.log(o.a);
|
||||||
|
}
|
||||||
|
expect_stdout: "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
single_use: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj = {
|
||||||
|
bar: function() {
|
||||||
|
return 42;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(obj.bar());
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log({
|
||||||
|
bar: function() {
|
||||||
|
return 42;
|
||||||
|
},
|
||||||
|
}.bar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name_collision_1: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var obj_foo = 1;
|
||||||
|
var obj_bar = 2;
|
||||||
|
function f() {
|
||||||
|
var obj = {
|
||||||
|
foo: 3,
|
||||||
|
bar: 4,
|
||||||
|
"b-r": 5,
|
||||||
|
"b+r": 6,
|
||||||
|
"b!r": 7,
|
||||||
|
};
|
||||||
|
console.log(obj_foo, obj.foo, obj.bar, obj["b-r"], obj["b+r"], obj["b!r"]);
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var obj_foo = 1;
|
||||||
|
var obj_bar = 2;
|
||||||
|
function f() {
|
||||||
|
var obj_foo$0 = 3,
|
||||||
|
obj_bar = 4,
|
||||||
|
obj_b_r = 5,
|
||||||
|
obj_b_r$0 = 6,
|
||||||
|
obj_b_r$1 = 7;
|
||||||
|
console.log(obj_foo, obj_foo$0, obj_bar, obj_b_r, obj_b_r$0, obj_b_r$1);
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
expect_stdout: "1 3 4 5 6 7"
|
||||||
|
}
|
||||||
|
|
||||||
|
name_collision_2: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
p: 1,
|
||||||
|
0: function(x) {
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
1: function(x) {
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
}, o__$0 = 2, o__$1 = 3;
|
||||||
|
console.log(o.p === o.p, o[0](4), o[1](5), o__$0, o__$1);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o_p = 1,
|
||||||
|
o__ = function(x) {
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
o__$2 = function(x) {
|
||||||
|
return x + 1;
|
||||||
|
},
|
||||||
|
o__$0 = 2,
|
||||||
|
o__$1 = 3;
|
||||||
|
console.log(o_p === o_p, o__(4), o__$2(5), o__$0, o__$1);
|
||||||
|
}
|
||||||
|
expect_stdout: "true 4 6 2 3"
|
||||||
|
}
|
||||||
|
|
||||||
|
name_collision_3: {
|
||||||
|
options = {
|
||||||
|
hoist_props: true,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
p: 1,
|
||||||
|
0: function(x) {
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
1: function(x) {
|
||||||
|
return x + 1;
|
||||||
|
}
|
||||||
|
}, o__$0 = 2, o__$1 = 3;
|
||||||
|
console.log(o.p === o.p, o[0](4), o[1](5));
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o_p = 1,
|
||||||
|
o__ = function(x) {
|
||||||
|
return x;
|
||||||
|
},
|
||||||
|
o__$2 = function(x) {
|
||||||
|
return x + 1;
|
||||||
|
},
|
||||||
|
o__$0 = 2,
|
||||||
|
o__$1 = 3;
|
||||||
|
console.log(o_p === o_p, o__(4), o__$2(5));
|
||||||
|
}
|
||||||
|
expect_stdout: "true 4 6"
|
||||||
|
}
|
||||||
|
|
||||||
|
contains_this_1: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
hoist_props: true,
|
||||||
|
inline: true,
|
||||||
|
passes: 2,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
u: function() {
|
||||||
|
return this === this;
|
||||||
|
},
|
||||||
|
p: 1
|
||||||
|
};
|
||||||
|
console.log(o.p, o.p);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(1, 1);
|
||||||
|
}
|
||||||
|
expect_stdout: "1 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
contains_this_2: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
hoist_props: true,
|
||||||
|
inline: true,
|
||||||
|
passes: 2,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
u: function() {
|
||||||
|
return this === this;
|
||||||
|
},
|
||||||
|
p: 1
|
||||||
|
};
|
||||||
|
console.log(o.p, o.p, o.u);
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
console.log(1, 1, function() {
|
||||||
|
return this === this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
expect_stdout: true
|
||||||
|
}
|
||||||
|
|
||||||
|
contains_this_3: {
|
||||||
|
options = {
|
||||||
|
evaluate: true,
|
||||||
|
hoist_props: true,
|
||||||
|
inline: true,
|
||||||
|
passes: 2,
|
||||||
|
reduce_vars: true,
|
||||||
|
toplevel: true,
|
||||||
|
unused: true,
|
||||||
|
}
|
||||||
|
input: {
|
||||||
|
var o = {
|
||||||
|
u: function() {
|
||||||
|
return this === this;
|
||||||
|
},
|
||||||
|
p: 1
|
||||||
|
};
|
||||||
|
console.log(o.p, o.p, o.u());
|
||||||
|
}
|
||||||
|
expect: {
|
||||||
|
var o = {
|
||||||
|
u: function() {
|
||||||
|
return this === this;
|
||||||
|
},
|
||||||
|
p: 1
|
||||||
|
};
|
||||||
|
console.log(o.p, o.p, o.u());
|
||||||
|
}
|
||||||
|
expect_stdout: "1 1 true"
|
||||||
|
}
|
||||||
@@ -16,11 +16,9 @@
|
|||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
"compress": {
|
"compress": {
|
||||||
"toplevel": true
|
"hoist_props": true
|
||||||
},
|
},
|
||||||
"mangle": {
|
"toplevel": true
|
||||||
"toplevel": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"compress": {
|
"compress": {
|
||||||
|
|||||||
Reference in New Issue
Block a user