aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/uglifyjs44
-rw-r--r--lib/propmangle.js211
-rw-r--r--lib/utils.js12
-rw-r--r--tools/node.js3
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));
});