Merge branch 'master' of git@github.com:mishoo/UglifyJS2.git into mishoo/UglifyJS2/Issue-935bareReturns
# Conflicts: # tools/node.js Somehow this got foo'd... merging mishoo:master in again... was checked out as master and branch was created off of that master
This commit is contained in:
commit
e28e807577
|
|
@ -628,6 +628,9 @@ Other options:
|
||||||
|
|
||||||
- `mangle` — pass `false` to skip mangling names.
|
- `mangle` — pass `false` to skip mangling names.
|
||||||
|
|
||||||
|
- `mangleProperties` (default `false`) — pass an object to specify custom
|
||||||
|
mangle property options.
|
||||||
|
|
||||||
- `output` (default `null`) — pass an object if you wish to specify
|
- `output` (default `null`) — pass an object if you wish to specify
|
||||||
additional [output options][codegen]. The defaults are optimized
|
additional [output options][codegen]. The defaults are optimized
|
||||||
for best compression.
|
for best compression.
|
||||||
|
|
@ -635,6 +638,10 @@ Other options:
|
||||||
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
|
- `compress` (default `{}`) — pass `false` to skip compressing entirely.
|
||||||
Pass an object to specify custom [compressor options][compressor].
|
Pass an object to specify custom [compressor options][compressor].
|
||||||
|
|
||||||
|
##### mangleProperties options
|
||||||
|
|
||||||
|
- `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option)
|
||||||
|
|
||||||
We could add more options to `UglifyJS.minify` — if you need additional
|
We could add more options to `UglifyJS.minify` — if you need additional
|
||||||
functionality please suggest!
|
functionality please suggest!
|
||||||
|
|
||||||
|
|
|
||||||
161
lib/compress.js
161
lib/compress.js
|
|
@ -66,6 +66,7 @@ function Compressor(options, false_by_default) {
|
||||||
hoist_vars : false,
|
hoist_vars : false,
|
||||||
if_return : !false_by_default,
|
if_return : !false_by_default,
|
||||||
join_vars : !false_by_default,
|
join_vars : !false_by_default,
|
||||||
|
collapse_vars : false,
|
||||||
cascade : !false_by_default,
|
cascade : !false_by_default,
|
||||||
side_effects : !false_by_default,
|
side_effects : !false_by_default,
|
||||||
pure_getters : false,
|
pure_getters : false,
|
||||||
|
|
@ -218,6 +219,9 @@ merge(Compressor.prototype, {
|
||||||
if (compressor.option("join_vars")) {
|
if (compressor.option("join_vars")) {
|
||||||
statements = join_consecutive_vars(statements, compressor);
|
statements = join_consecutive_vars(statements, compressor);
|
||||||
}
|
}
|
||||||
|
if (compressor.option("collapse_vars")) {
|
||||||
|
statements = collapse_single_use_vars(statements, compressor);
|
||||||
|
}
|
||||||
} while (CHANGED && max_iter-- > 0);
|
} while (CHANGED && max_iter-- > 0);
|
||||||
|
|
||||||
if (compressor.option("negate_iife")) {
|
if (compressor.option("negate_iife")) {
|
||||||
|
|
@ -226,6 +230,163 @@ merge(Compressor.prototype, {
|
||||||
|
|
||||||
return statements;
|
return statements;
|
||||||
|
|
||||||
|
function collapse_single_use_vars(statements, compressor) {
|
||||||
|
// Iterate statements backwards looking for a statement with a var/const
|
||||||
|
// declaration immediately preceding it. Grab the rightmost var definition
|
||||||
|
// and if it has exactly one reference then attempt to replace its reference
|
||||||
|
// in the statement with the var value and then erase the var definition.
|
||||||
|
|
||||||
|
var self = compressor.self();
|
||||||
|
var var_defs_removed = false;
|
||||||
|
for (var stat_index = statements.length; --stat_index >= 0;) {
|
||||||
|
var stat = statements[stat_index];
|
||||||
|
if (stat instanceof AST_Definitions) continue;
|
||||||
|
|
||||||
|
// Process child blocks of statement if present.
|
||||||
|
[stat, stat.body, stat.alternative, stat.bcatch, stat.bfinally].forEach(function(node) {
|
||||||
|
node && node.body && collapse_single_use_vars(node.body, compressor);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The variable definition must precede a statement.
|
||||||
|
if (stat_index <= 0) break;
|
||||||
|
var prev_stat_index = stat_index - 1;
|
||||||
|
var prev_stat = statements[prev_stat_index];
|
||||||
|
if (!(prev_stat instanceof AST_Definitions)) continue;
|
||||||
|
var var_defs = prev_stat.definitions;
|
||||||
|
if (var_defs == null) continue;
|
||||||
|
|
||||||
|
var var_names_seen = {};
|
||||||
|
var side_effects_encountered = false;
|
||||||
|
var lvalues_encountered = false;
|
||||||
|
var lvalues = {};
|
||||||
|
|
||||||
|
// Scan variable definitions from right to left.
|
||||||
|
for (var var_defs_index = var_defs.length; --var_defs_index >= 0;) {
|
||||||
|
|
||||||
|
// Obtain var declaration and var name with basic sanity check.
|
||||||
|
var var_decl = var_defs[var_defs_index];
|
||||||
|
if (var_decl.value == null) break;
|
||||||
|
var var_name = var_decl.name.name;
|
||||||
|
if (!var_name || !var_name.length) break;
|
||||||
|
|
||||||
|
// Bail if we've seen a var definition of same name before.
|
||||||
|
if (var_name in var_names_seen) break;
|
||||||
|
var_names_seen[var_name] = true;
|
||||||
|
|
||||||
|
// Only interested in cases with just one reference to the variable.
|
||||||
|
var def = self.find_variable && self.find_variable(var_name);
|
||||||
|
if (!def || !def.references || def.references.length !== 1 || var_name == "arguments") {
|
||||||
|
side_effects_encountered = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var ref = def.references[0];
|
||||||
|
|
||||||
|
// Don't replace ref if eval() or with statement in scope.
|
||||||
|
if (ref.scope.uses_eval || ref.scope.uses_with) break;
|
||||||
|
|
||||||
|
// Constant single use vars can be replaced in any scope.
|
||||||
|
if (var_decl.value.is_constant(compressor)) {
|
||||||
|
var ctt = new TreeTransformer(function(node) {
|
||||||
|
if (node === ref)
|
||||||
|
return replace_var(node, ctt.parent(), true);
|
||||||
|
});
|
||||||
|
stat.transform(ctt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict var replacement to constants if side effects encountered.
|
||||||
|
if (side_effects_encountered |= lvalues_encountered) continue;
|
||||||
|
|
||||||
|
// Non-constant single use vars can only be replaced in same scope.
|
||||||
|
if (ref.scope !== self) {
|
||||||
|
side_effects_encountered |= var_decl.value.has_side_effects(compressor);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect lvalues in var value.
|
||||||
|
var tw = new TreeWalker(function(node){
|
||||||
|
if (node instanceof AST_SymbolRef && is_lvalue(node, tw.parent())) {
|
||||||
|
lvalues[node.name] = lvalues_encountered = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var_decl.value.walk(tw);
|
||||||
|
|
||||||
|
// Replace the non-constant single use var in statement if side effect free.
|
||||||
|
var unwind = false;
|
||||||
|
var tt = new TreeTransformer(
|
||||||
|
function preorder(node) {
|
||||||
|
if (unwind) return node;
|
||||||
|
var parent = tt.parent();
|
||||||
|
if (node instanceof AST_Lambda
|
||||||
|
|| node instanceof AST_Try
|
||||||
|
|| node instanceof AST_With
|
||||||
|
|| node instanceof AST_Case
|
||||||
|
|| node instanceof AST_IterationStatement
|
||||||
|
|| (parent instanceof AST_If && node !== parent.condition)
|
||||||
|
|| (parent instanceof AST_Conditional && node !== parent.condition)
|
||||||
|
|| (parent instanceof AST_Binary
|
||||||
|
&& (parent.operator == "&&" || parent.operator == "||")
|
||||||
|
&& node === parent.right)
|
||||||
|
|| (parent instanceof AST_Switch && node !== parent.expression)) {
|
||||||
|
return side_effects_encountered = unwind = true, node;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function postorder(node) {
|
||||||
|
if (unwind) return node;
|
||||||
|
if (node === ref)
|
||||||
|
return unwind = true, replace_var(node, tt.parent(), false);
|
||||||
|
if (side_effects_encountered |= node.has_side_effects(compressor))
|
||||||
|
return unwind = true, node;
|
||||||
|
if (lvalues_encountered && node instanceof AST_SymbolRef && node.name in lvalues) {
|
||||||
|
side_effects_encountered = true;
|
||||||
|
return unwind = true, node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
stat.transform(tt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove extraneous empty statments in block after removing var definitions.
|
||||||
|
// Leave at least one statement in `statements`.
|
||||||
|
if (var_defs_removed) for (var i = statements.length; --i >= 0;) {
|
||||||
|
if (statements.length > 1 && statements[i] instanceof AST_EmptyStatement)
|
||||||
|
statements.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return statements;
|
||||||
|
|
||||||
|
function is_lvalue(node, parent) {
|
||||||
|
return node instanceof AST_SymbolRef && (
|
||||||
|
(parent instanceof AST_Assign && node === parent.left)
|
||||||
|
|| (parent instanceof AST_Unary && parent.expression === node
|
||||||
|
&& (parent.operator == "++" || parent.operator == "--")));
|
||||||
|
}
|
||||||
|
function replace_var(node, parent, is_constant) {
|
||||||
|
if (is_lvalue(node, parent)) return node;
|
||||||
|
|
||||||
|
// Remove var definition and return its value to the TreeTransformer to replace.
|
||||||
|
var value = var_decl.value;
|
||||||
|
var_decl.value = null;
|
||||||
|
|
||||||
|
var_defs.splice(var_defs_index, 1);
|
||||||
|
if (var_defs.length === 0) {
|
||||||
|
statements[prev_stat_index] = make_node(AST_EmptyStatement, self);
|
||||||
|
var_defs_removed = true;
|
||||||
|
}
|
||||||
|
// Further optimize statement after substitution.
|
||||||
|
stat.walk(new TreeWalker(function(node){
|
||||||
|
delete node._squeezed;
|
||||||
|
delete node._optimized;
|
||||||
|
}));
|
||||||
|
|
||||||
|
compressor.warn("Replacing " + (is_constant ? "constant" : "variable") +
|
||||||
|
" " + var_name + " [{file}:{line},{col}]", node.start);
|
||||||
|
CHANGED = true;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function process_for_angular(statements) {
|
function process_for_angular(statements) {
|
||||||
function has_inject(comment) {
|
function has_inject(comment) {
|
||||||
return /@ngInject/.test(comment.value);
|
return /@ngInject/.test(comment.value);
|
||||||
|
|
|
||||||
|
|
@ -242,7 +242,7 @@ AST_Lambda.DEFMETHOD("init_scope_vars", function(){
|
||||||
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
|
AST_Scope.prototype.init_scope_vars.apply(this, arguments);
|
||||||
this.uses_arguments = false;
|
this.uses_arguments = false;
|
||||||
|
|
||||||
var symbol = new AST_VarDef({ name: "arguments" });
|
var symbol = new AST_VarDef({ name: "arguments", start: this.start, end: this.end });
|
||||||
var def = new SymbolDef(this, this.variables.size(), symbol);
|
var def = new SymbolDef(this, this.variables.size(), symbol);
|
||||||
this.variables.set(symbol.name, def);
|
this.variables.set(symbol.name, def);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ TreeTransformer.prototype = new TreeWalker;
|
||||||
x = this;
|
x = this;
|
||||||
descend(x, tw);
|
descend(x, tw);
|
||||||
} else {
|
} else {
|
||||||
tw.stack[tw.stack.length - 1] = x = this.clone();
|
tw.stack[tw.stack.length - 1] = x = this;
|
||||||
descend(x, tw);
|
descend(x, tw);
|
||||||
y = tw.after(x, in_list);
|
y = tw.after(x, in_list);
|
||||||
if (y !== undefined) x = y;
|
if (y !== undefined) x = y;
|
||||||
|
|
|
||||||
1155
test/compress/collapse_vars.js
Normal file
1155
test/compress/collapse_vars.js
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -39,6 +39,8 @@ exports.minify = function(files, options) {
|
||||||
fromString : false,
|
fromString : false,
|
||||||
warnings : false,
|
warnings : false,
|
||||||
mangle : {},
|
mangle : {},
|
||||||
|
mangleProperties : false,
|
||||||
|
nameCache : null,
|
||||||
output : null,
|
output : null,
|
||||||
compress : {},
|
compress : {},
|
||||||
parse : {}
|
parse : {}
|
||||||
|
|
@ -79,14 +81,21 @@ exports.minify = function(files, options) {
|
||||||
toplevel = toplevel.transform(sq);
|
toplevel = toplevel.transform(sq);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. mangle
|
// 3. mangle properties
|
||||||
|
if (options.mangleProperties || options.nameCache) {
|
||||||
|
options.mangleProperties.cache = UglifyJS.readNameCache(options.nameCache, "props");
|
||||||
|
toplevel = UglifyJS.mangle_properties(toplevel, options.mangleProperties);
|
||||||
|
UglifyJS.writeNameCache(options.nameCache, "props", options.mangleProperties.cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. mangle
|
||||||
if (options.mangle) {
|
if (options.mangle) {
|
||||||
toplevel.figure_out_scope(options.mangle);
|
toplevel.figure_out_scope(options.mangle);
|
||||||
toplevel.compute_char_frequency(options.mangle);
|
toplevel.compute_char_frequency(options.mangle);
|
||||||
toplevel.mangle_names(options.mangle);
|
toplevel.mangle_names(options.mangle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. output
|
// 5. output
|
||||||
var inMap = options.inSourceMap;
|
var inMap = options.inSourceMap;
|
||||||
var output = {};
|
var output = {};
|
||||||
if (typeof options.inSourceMap == "string") {
|
if (typeof options.inSourceMap == "string") {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user