diff --git a/bin/uglifyjs b/bin/uglifyjs index 0bc21e89..79583094 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -21,6 +21,7 @@ var options = { unsafe: false, reserved_names: null, defines: { }, + lift_vars: false, codegen_options: { ascii_only: false, beautify: false, @@ -97,6 +98,9 @@ out: while (args.length > 0) { case "--reserved-names": options.reserved_names = args.shift().split(","); break; + case "--lift-vars": + options.lift_vars = true; + break; case "-d": case "--define": var defarg = args.shift(); @@ -269,7 +273,9 @@ function squeeze_it(code) { } try { var ast = time_it("parse", function(){ return jsp.parse(code); }); - //ast = time_it("lift", function(){ return pro.ast_lift_variables(ast); }); + if (options.lift_vars) { + ast = time_it("lift", function(){ return pro.ast_lift_variables(ast); }); + } if (options.mangle) ast = time_it("mangle", function(){ return pro.ast_mangle(ast, { toplevel: options.mangle_toplevel, diff --git a/lib/process.js b/lib/process.js index 1e2ab838..e59507bb 100644 --- a/lib/process.js +++ b/lib/process.js @@ -836,18 +836,96 @@ function prepare_ifs(ast) { }); }; +function for_side_effects(ast, handler) { + var w = ast_walker(), walk = w.walk; + var $stop = {}, $restart = {}; + function stop() { throw $stop }; + function restart() { throw $restart }; + function found(){ return handler.call(this, this, w, stop, restart) }; + return w.with_walkers({ + "try": found, + "throw": found, + "return": found, + "new": found, + "switch": found, + "break": found, + "continue": found, + "assign": found, + "call": found, + "if": found, + "for": found, + "for-in": found, + "while": found, + "do": found, + "return": found, + "unary-prefix": function(op) { + if (op == "++" || op == "--") + return found.apply(this, arguments); + }, + "unary-postfix": function(op) { + if (op == "++" || op == "--") + return found.apply(this, arguments); + } + }, function(){ + while (true) try { + walk(ast); + break; + } catch(ex) { + if (ex === $stop) break; + if (ex === $restart) continue; + throw ex; + } + }); +}; + function ast_lift_variables(ast) { var w = ast_walker(), walk = w.walk, scope; function do_body(body, env) { var _scope = scope; scope = env; body = MAP(body, walk); - var names = MAP(env.names, function(type, name){ + var hash = {}, names = MAP(env.names, function(type, name){ if (type != "var") return MAP.skip; if (!env.references(name)) return MAP.skip; + hash[name] = true; return [ name ]; }); if (names.length > 0) { + // looking for assignments to any of these variables. + // we can save considerable space by moving the definitions + // in the var declaration. + for_side_effects([ "block", body ], function(ast, walker, stop, restart) { + if (ast[0] == "assign" + && ast[1] === true + && ast[2][0] == "name" + && HOP(hash, ast[2][1])) { + // insert the definition into the var declaration + for (var i = names.length; --i >= 0;) { + if (names[i][0] == ast[2][1]) { + if (names[i][1]) // this name already defined, we must stop + stop(); + names[i][1] = ast[3]; // definition + names.push(names.splice(i, 1)[0]); + break; + } + } + // remove this assignment from the AST. + var p = walker.parent(); + if (p[0] == "seq") { + var a = p[2]; + a.unshift(0, p.length); + p.splice.apply(p, a); + } + else if (p[0] == "stat") { + p.splice(0, p.length, "block"); // empty statement + } + else { + stop(); + } + restart(); + } + stop(); + }); body.unshift([ "var", names ]); } scope = _scope; diff --git a/tmp/hoist.js b/tmp/hoist.js index 95598001..4bf2b94d 100644 --- a/tmp/hoist.js +++ b/tmp/hoist.js @@ -1,13 +1,16 @@ function foo(arg1, arg2, arg3, arg4, arg5, arg6) { - var a = 5, arg4 = 1; - var d = 10, mak = 20, buz = 30; + var a = 5; + { + var d = 10, mak = 20, buz = 30; + var q = buz * 2; + } if (moo) { var a, b, c; } for (var arg1 = 0, d = 20; arg1 < 10; ++arg1) console.log(arg3); for (var i in mak) {} - for (j in buz) {} + for (j in d) {} var d; function test() {