diff options
-rwxr-xr-x | bin/uglifyjs | 44 | ||||
-rw-r--r-- | lib/propmangle.js | 211 | ||||
-rw-r--r-- | lib/utils.js | 12 | ||||
-rw-r--r-- | tools/node.js | 3 |
4 files changed, 267 insertions, 3 deletions
diff --git a/bin/uglifyjs b/bin/uglifyjs index 31133c93..ca7212c0 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -67,6 +67,9 @@ You need to pass an argument to this option to specify the name that your module .describe("bare-returns", "Allow return outside of functions. Useful when minifying CommonJS modules.") .describe("keep-fnames", "Do not mangle/drop function names. Useful for code relying on Function.prototype.name.") .describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)") + .describe("reserved-file", "File containing reserved names") + .describe("mangle-props", "Mangle property names") + .describe("prop-cache", "File to hold mangled properties mapping") .alias("p", "prefix") .alias("o", "output") @@ -91,6 +94,8 @@ You need to pass an argument to this option to specify the name that your module .string("comments") .string("wrap") .string("p") + .string("reserved-file") + .string("prop-cache") .boolean("expr") .boolean("source-map-include-sources") @@ -106,6 +111,7 @@ You need to pass an argument to this option to specify the name that your module .boolean("noerr") .boolean("bare-returns") .boolean("keep-fnames") + .boolean("mangle-props") .wrap(80) @@ -153,6 +159,16 @@ if (ARGS.r) { if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/); } +if (ARGS.reserved_file) (function(){ + var data = fs.readFileSync(ARGS.reserved_file, "utf8"); + RESERVED = data = JSON.parse(data); + if (data.vars) { + MANGLE.except = MANGLE.except + ? MANGLE.except.concat(data.vars) + : data.vars; + } +})(); + if (ARGS.quotes === true) { ARGS.quotes = 3; } @@ -242,6 +258,7 @@ var OUTPUT_FILE = ARGS.o; var TOPLEVEL = null; var P_RELATIVE = ARGS.p && ARGS.p == "relative"; var SOURCES_CONTENT = {}; +var RESERVED = null; var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({ file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE, @@ -334,6 +351,33 @@ async.eachLimit(files, 1, function (file, cb) { TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list); } + if (ARGS.mangle_props) (function(){ + var reserved = RESERVED ? RESERVED.props : null; + var cache = null; + if (ARGS.prop_cache) { + try { + cache = fs.readFileSync(ARGS.prop_cache, "utf8"); + cache = JSON.parse(cache); + cache.props = UglifyJS.Dictionary.fromObject(cache.props); + } catch(ex) { + cache = { + cname: -1, + props: new UglifyJS.Dictionary() + }; + } + } + TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, { + reserved: reserved, + cache: cache + }); + if (ARGS.prop_cache) { + fs.writeFileSync(ARGS.prop_cache, JSON.stringify({ + cname: cache.cname, + props: cache.props.toObject() + }, null, 2), "utf8"); + } + })(); + var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint; if (SCOPE_IS_NEEDED) { diff --git a/lib/propmangle.js b/lib/propmangle.js new file mode 100644 index 00000000..41eeffe4 --- /dev/null +++ b/lib/propmangle.js @@ -0,0 +1,211 @@ +/*********************************************************************** + + A JavaScript tokenizer / parser / beautifier / compressor. + https://github.com/mishoo/UglifyJS2 + + -------------------------------- (C) --------------------------------- + + Author: Mihai Bazon + <mihai.bazon@gmail.com> + http://mihai.bazon.net/blog + + Distributed under the BSD license: + + Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com> + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + ***********************************************************************/ + +"use strict"; + +function find_builtins() { + var a = []; + [ Object, Array, Function, Number, + String, Boolean, Error, Math, + Date, RegExp + ].forEach(function(ctor){ + Object.getOwnPropertyNames(ctor).map(add); + if (ctor.prototype) { + Object.getOwnPropertyNames(ctor.prototype).map(add); + } + }); + function add(name) { + push_uniq(a, name); + } + return a; +} + +function mangle_properties(ast, options) { + options = defaults(options, { + reserved : null, + cache : null + }); + + var reserved = options.reserved; + if (reserved == null) + reserved = find_builtins(); + + var cache = options.cache; + if (cache == null) { + cache = { + cname: -1, + props: new Dictionary() + }; + } + + var names_to_mangle = []; + + // step 1: find candidates to mangle + ast.walk(new TreeWalker(function(node){ + if (node instanceof AST_ObjectKeyVal) { + add(node.key); + } + else if (node instanceof AST_ObjectProperty) { + // setter or getter, since KeyVal is handled above + add(node.key.name); + } + else if (node instanceof AST_Dot) { + if (this.parent() instanceof AST_Assign) { + add(node.property); + } + } + else if (node instanceof AST_Sub) { + if (this.parent() instanceof AST_Assign) { + addStrings(node.property); + } + } + })); + + // step 2: transform the tree, renaming properties + return ast.transform(new TreeTransformer(null, function(node){ + if (node instanceof AST_ObjectKeyVal) { + if (should_mangle(node.key)) { + node.key = mangle(node.key); + } + } + else if (node instanceof AST_ObjectProperty) { + // setter or getter + if (should_mangle(node.key.name)) { + node.key.name = mangle(node.key.name); + } + } + else if (node instanceof AST_Dot) { + if (should_mangle(node.property)) { + node.property = mangle(node.property); + } + } + else if (node instanceof AST_Sub) { + node.property = mangleStrings(node.property); + } + // else if (node instanceof AST_String) { + // if (should_mangle(node.value)) { + // AST_Node.warn( + // "Found \"{prop}\" property candidate for mangling in an arbitrary string [{file}:{line},{col}]", { + // file : node.start.file, + // line : node.start.line, + // col : node.start.col, + // prop : node.value + // } + // ); + // } + // } + })); + + // only function declarations after this line + + function can_mangle(name) { + if (reserved.indexOf(name) >= 0) return false; + if (/^[0-9.]+$/.test(name)) return false; + return true; + } + + function should_mangle(name) { + return names_to_mangle.indexOf(name) >= 0; + } + + function add(name) { + if (can_mangle(name)) + push_uniq(names_to_mangle, name); + } + + function mangle(name) { + var mangled = cache.props.get(name); + if (!mangled) { + do { + mangled = base54(++cache.cname); + } while (!can_mangle(mangled)); + cache.props.set(name, mangled); + } + return mangled; + } + + function addStrings(node) { + var out = {}; + try { + (function walk(node){ + node.walk(new TreeWalker(function(node){ + if (node instanceof AST_Seq) { + walk(node.cdr); + return true; + } + if (node instanceof AST_String) { + add(node.value); + return true; + } + if (node instanceof AST_Conditional) { + walk(node.consequent); + walk(node.alternative); + return true; + } + throw out; + })); + })(node); + } catch(ex) { + if (ex !== out) throw ex; + } + } + + function mangleStrings(node) { + return node.transform(new TreeTransformer(function(node){ + if (node instanceof AST_Seq) { + node.cdr = mangleStrings(node.cdr); + } + else if (node instanceof AST_String) { + if (should_mangle(node.value)) { + node.value = mangle(node.value); + } + } + else if (node instanceof AST_Conditional) { + node.consequent = mangleStrings(node.consequent); + node.alternative = mangleStrings(node.alternative); + } + return node; + })); + } + +} diff --git a/lib/utils.js b/lib/utils.js index 7c6a1563..4612a322 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -106,10 +106,12 @@ function defaults(args, defs, croak) { }; function merge(obj, ext) { + var count = 0; for (var i in ext) if (ext.hasOwnProperty(i)) { obj[i] = ext[i]; + count++; } - return obj; + return count; }; function noop() {}; @@ -298,5 +300,11 @@ Dictionary.prototype = { for (var i in this._values) ret.push(f(this._values[i], i.substr(1))); return ret; - } + }, + toObject: function() { return this._values } +}; +Dictionary.fromObject = function(obj) { + var dict = new Dictionary(); + dict._size = merge(dict._values, obj); + return dict; }; diff --git a/tools/node.js b/tools/node.js index 1ae69da8..af540e0c 100644 --- a/tools/node.js +++ b/tools/node.js @@ -33,7 +33,8 @@ var FILES = exports.FILES = [ "../lib/output.js", "../lib/compress.js", "../lib/sourcemap.js", - "../lib/mozilla-ast.js" + "../lib/mozilla-ast.js", + "../lib/propmangle.js" ].map(function(file){ return fs.realpathSync(path.join(path.dirname(__filename), file)); }); |