diff --git a/README.md b/README.md index 51d0b629..293608e7 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,16 @@ The available options are: --reserved-file File containing reserved names --reserve-domprops Make (most?) DOM properties reserved for --mangle-props - --mangle-props Mangle property names + --mangle-props Mangle property names (default `0`). Set to + `true` or `1` to mangle all property names. Set + to `unquoted` or `2` to only mangle unquoted + property names. Mode `2` also enables the + `keep_quoted_props` beautifier option to + preserve the quotes around property names and + disables the `properties` compressor option to + prevent rewriting quoted properties with dot + notation. You can override these by setting + them explicitly on the command line. --mangle-regex Only mangle property names matching the regex --name-cache File to hold mangled names mappings --pure-funcs List of functions that can be safely removed if @@ -479,6 +488,8 @@ can pass additional arguments that control the code output: - `1` -- always use single quotes - `2` -- always use double quotes - `3` -- always use the original quotes +- `keep_quoted_props` (default `false`) -- when turned on, prevents stripping + quotes from property names in object literals. ### Keeping copyright notices or other comments @@ -667,7 +678,8 @@ Other options: ##### mangleProperties options - - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mange-regex` CLI arguments option) + - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) + - `ignore_quoted` – Only mangle unquoted property names (maps to the `--mangle-props 2` CLI arguments option) We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! diff --git a/bin/uglifyjs b/bin/uglifyjs index 45c92b50..b7009426 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -69,7 +69,7 @@ You need to pass an argument to this option to specify the name that your module .describe("quotes", "Quote style (0 - auto, 1 - single, 2 - double, 3 - original)") .describe("reserved-file", "File containing reserved names") .describe("reserve-domprops", "Make (most?) DOM properties reserved for --mangle-props") - .describe("mangle-props", "Mangle property names") + .describe("mangle-props", "Mangle property names (0 - disabled, 1 - mangle all properties, 2 - mangle unquoted properies)") .describe("mangle-regex", "Only mangle property names matching the regex") .describe("name-cache", "File to hold mangled names mappings") .describe("pure-funcs", "List of functions that can be safely removed if their return value is not used") @@ -125,7 +125,6 @@ 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") .boolean("reserve-domprops") .wrap(80) @@ -213,12 +212,24 @@ if (ARGS.quotes === true) { ARGS.quotes = 3; } +if (ARGS.mangle_props === true) { + ARGS.mangle_props = 1; +} else if (ARGS.mangle_props === "unquoted") { + ARGS.mangle_props = 2; +} + var OUTPUT_OPTIONS = { beautify : BEAUTIFY ? true : false, preamble : ARGS.preamble || null, quote_style : ARGS.quotes != null ? ARGS.quotes : 0 }; +if (ARGS.mangle_props == 2) { + OUTPUT_OPTIONS.keep_quoted_props = true; + if (COMPRESS && !("properties" in COMPRESS)) + COMPRESS.properties = false; +} + if (ARGS.screw_ie8) { if (COMPRESS) COMPRESS.screw_ie8 = true; if (MANGLE) MANGLE.screw_ie8 = true; @@ -401,10 +412,11 @@ async.eachLimit(files, 1, function (file, cb) { } TOPLEVEL = UglifyJS.mangle_properties(TOPLEVEL, { - reserved : reserved, - cache : cache, - only_cache : !ARGS.mangle_props, - regex : regex + reserved : reserved, + cache : cache, + only_cache : !ARGS.mangle_props, + regex : regex, + ignore_quoted : ARGS.mangle_props == 2 }); writeNameCache("props", cache); })(); diff --git a/lib/output.js b/lib/output.js index 1fa9899f..10465e21 100644 --- a/lib/output.js +++ b/lib/output.js @@ -66,7 +66,8 @@ function OutputStream(options) { preserve_line : false, screw_ie8 : false, preamble : null, - quote_style : 0 + quote_style : 0, + keep_quoted_props: false }, true); var indentation = 0; @@ -1173,7 +1174,11 @@ function OutputStream(options) { && parseFloat(key) >= 0) { output.print(make_num(key)); } else if (RESERVED_WORDS(key) ? output.option("screw_ie8") : is_identifier_string(key)) { - output.print_name(key); + if (quote && output.option("keep_quoted_props")) { + output.print_string(key, quote); + } else { + output.print_name(key); + } } else { output.print_string(key, quote); } diff --git a/lib/propmangle.js b/lib/propmangle.js index 840bda91..08043d73 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -65,7 +65,8 @@ function mangle_properties(ast, options) { reserved : null, cache : null, only_cache : false, - regex : null + regex : null, + ignore_quoted : false }); var reserved = options.reserved; @@ -81,6 +82,7 @@ function mangle_properties(ast, options) { } var regex = options.regex; + var ignore_quoted = options.ignore_quoted; var names_to_mangle = []; var unmangleable = []; @@ -88,7 +90,8 @@ function mangle_properties(ast, options) { // step 1: find candidates to mangle ast.walk(new TreeWalker(function(node){ if (node instanceof AST_ObjectKeyVal) { - add(node.key); + if (!(ignore_quoted && node.quote)) + add(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter, since KeyVal is handled above @@ -101,7 +104,8 @@ function mangle_properties(ast, options) { } else if (node instanceof AST_Sub) { if (this.parent() instanceof AST_Assign) { - addStrings(node.property); + if (!ignore_quoted) + addStrings(node.property); } } })); @@ -109,7 +113,8 @@ function mangle_properties(ast, options) { // step 2: transform the tree, renaming properties return ast.transform(new TreeTransformer(function(node){ if (node instanceof AST_ObjectKeyVal) { - node.key = mangle(node.key); + if (!(ignore_quoted && node.quote)) + node.key = mangle(node.key); } else if (node instanceof AST_ObjectProperty) { // setter or getter @@ -119,7 +124,8 @@ function mangle_properties(ast, options) { node.property = mangle(node.property); } else if (node instanceof AST_Sub) { - node.property = mangleStrings(node.property); + if (!ignore_quoted) + node.property = mangleStrings(node.property); } // else if (node instanceof AST_String) { // if (should_mangle(node.value)) { diff --git a/test/compress/properties.js b/test/compress/properties.js index 39470738..574c5142 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -72,3 +72,54 @@ evaluate_length: { a = ("foo" + b).length; } } + +mangle_properties: { + mangle_props = { + ignore_quoted: false + }; + input: { + a["foo"] = "bar"; + a.color = "red"; + x = {"bar": 10}; + } + expect: { + a["a"] = "bar"; + a.b = "red"; + x = {c: 10}; + } +} + +mangle_unquoted_properties: { + mangle_props = { + ignore_quoted: true + } + beautify = { + beautify: false, + quote_style: 3, + keep_quoted_props: true, + } + input: { + function f1() { + a["foo"] = "bar"; + a.color = "red"; + x = {"bar": 10}; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10}; + } + } + expect: { + function f1() { + a["foo"] = "bar"; + a.a = "red"; + x = {"bar": 10}; + } + function f2() { + a.b = "bar"; + a['color'] = "red"; + x = {c: 10}; + } + } +} diff --git a/test/mocha/minify.js b/test/mocha/minify.js index bbf188c4..02d31558 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -7,5 +7,56 @@ describe("minify", function() { var result = Uglify.minify(js, {fromString: true}); assert.strictEqual(result.code, 'function foo(n){return n?3:7}'); }); -}); + describe("keep_quoted_props", function() { + it("Should preserve quotes in object literals", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: true + }}); + assert.strictEqual(result.code, 'var foo={"x":1,y:2,"z":3};'); + }); + + it("Should preserve quote styles when quote_style is 3", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: true, + quote_style: 3 + }}); + assert.strictEqual(result.code, 'var foo={"x":1,y:2,\'z\':3};'); + }); + + it("Should not preserve quotes in object literals when disabled", function() { + var js = 'var foo = {"x": 1, y: 2, \'z\': 3};'; + var result = Uglify.minify(js, { + fromString: true, output: { + keep_quoted_props: false, + quote_style: 3 + }}); + assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); + }); + }); + + describe("mangleProperties", function() { + it("Shouldn't mangle quoted properties", function() { + var js = 'a["foo"] = "bar"; a.color = "red"; x = {"bar": 10};'; + var result = Uglify.minify(js, { + fromString: true, + compress: { + properties: false + }, + mangleProperties: { + ignore_quoted: true + }, + output: { + keep_quoted_props: true, + quote_style: 3 + } + }); + assert.strictEqual(result.code, + 'a["foo"]="bar",a.a="red",x={"bar":10};'); + }); + }); +}); diff --git a/test/run-tests.js b/test/run-tests.js index 5fc69c69..0fdee6f1 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -108,7 +108,11 @@ function run_compress_tests() { expect = test.expect_exact; } var input = as_toplevel(test.input); - var input_code = make_code(test.input, { beautify: true }); + var input_code = make_code(test.input, { + beautify: true, + quote_style: 3, + keep_quoted_props: true + }); if (test.mangle_props) { input = U.mangle_properties(input, test.mangle_props); }