From: Alex Lam S.L Date: Thu, 29 Jun 2017 04:48:34 +0000 (+0800) Subject: improve usability of name cache under `minify()` (#2176) X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=bdeadffbf582b393dbc14a45b3e69ddf16f47690;p=UglifyJS.git improve usability of name cache under `minify()` (#2176) fixes #2174 --- diff --git a/README.md b/README.md index de2c2c95..c277f653 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ a double dash to prevent input files being used as option arguments: By default UglifyJS will not try to be IE-proof. --keep-fnames Do not mangle/drop function names. Useful for code relying on Function.prototype.name. - --name-cache File to hold mangled name mappings. + --name-cache File to hold mangled name mappings. --self Build UglifyJS as a library (implies --wrap UglifyJS) --source-map [options] Enable source map/specify source map options: `base` Path to compute relative paths from input files. @@ -383,7 +383,47 @@ var code = { var options = { toplevel: true }; var result = UglifyJS.minify(code, options); console.log(result.code); -// console.log(function(n,o){return n+o}(3,7)); +// console.log(3+7); +``` + +The `nameCache` option: +```javascript +var options = { + mangle: { + toplevel: true, + }, + nameCache: {} +}; +var result1 = UglifyJS.minify({ + "file1.js": "function add(first, second) { return first + second; }" +}, options); +var result2 = UglifyJS.minify({ + "file2.js": "console.log(add(1 + 2, 3 + 4));" +}, options); +console.log(result1.code); +// function n(n,r){return n+r} +console.log(result2.code); +// console.log(n(3,7)); +``` + +You may persist the name cache to the file system in the following way: +```javascript +var cacheFileName = "/tmp/cache.json"; +var options = { + mangle: { + properties: true, + }, + nameCache: JSON.parse(fs.readFileSync(cacheFileName, "utf8")) +}; +fs.writeFileSync("part1.js", UglifyJS.minify({ + "file1.js": fs.readFileSync("file1.js", "utf8"), + "file2.js": fs.readFileSync("file2.js", "utf8") +}, options).code, "utf8"); +fs.writeFileSync("part2.js", UglifyJS.minify({ + "file3.js": fs.readFileSync("file3.js", "utf8"), + "file4.js": fs.readFileSync("file4.js", "utf8") +}, options).code, "utf8"); +fs.writeFileSync(cacheFileName, JSON.stringify(options.nameCache), "utf8"); ``` An example of a combination of `minify()` options: @@ -461,6 +501,13 @@ if (result.error) throw result.error; - `toplevel` (default `false`) - set to `true` if you wish to enable top level variable and function name mangling and to drop unused variables and functions. +- `nameCache` (default `null`) - pass an empty object `{}` or a previously + used `nameCache` object if you wish to cache mangled variable and + property names across multiple invocations of `minify()`. Note: this is + a read/write property. `minify()` will read the name cache state of this + object and update it during minification so that it may be + reused or externally persisted by the user. + - `ie8` (default `false`) - set to `true` to support IE8. ## Minify options structure @@ -487,6 +534,7 @@ if (result.error) throw result.error; sourceMap: { // source map options }, + nameCache: null, // or specify a name cache object toplevel: false, ie8: false, } diff --git a/bin/uglifyjs b/bin/uglifyjs index f4feb39a..68d67c2f 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -106,17 +106,8 @@ if (program.mangleProps) { if (typeof options.mangle != "object") options.mangle = {}; options.mangle.properties = program.mangleProps; } -var cache; if (program.nameCache) { - cache = JSON.parse(read_file(program.nameCache, "{}")); - if (options.mangle) { - if (typeof options.mangle != "object") options.mangle = {}; - options.mangle.cache = to_cache("vars"); - if (options.mangle.properties) { - if (typeof options.mangle.properties != "object") options.mangle.properties = {}; - options.mangle.properties.cache = to_cache("props"); - } - } + options.nameCache = JSON.parse(read_file(program.nameCache, "{}")); } if (program.output == "ast") { options.output = { @@ -266,9 +257,7 @@ function run() { print(result.code); } if (program.nameCache) { - fs.writeFileSync(program.nameCache, JSON.stringify(cache, function(key, value) { - return value instanceof UglifyJS.Dictionary ? value.toObject() : value; - })); + fs.writeFileSync(program.nameCache, JSON.stringify(options.nameCache)); } if (result.timings) for (var phase in result.timings) { print_error("- " + phase + ": " + result.timings[phase].toFixed(3) + "s"); @@ -381,18 +370,6 @@ function parse_source_map() { } } -function to_cache(key) { - if (cache[key]) { - cache[key].props = UglifyJS.Dictionary.fromObject(cache[key].props); - } else { - cache[key] = { - cname: -1, - props: new UglifyJS.Dictionary() - }; - } - return cache[key]; -} - function skip_key(key) { return skip_keys.indexOf(key) >= 0; } diff --git a/lib/minify.js b/lib/minify.js index cc638be3..b4bfe451 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -27,6 +27,23 @@ function set_shorthand(name, options, keys) { } } +function init_cache(cache) { + if (!cache) return; + if (!("cname" in cache)) cache.cname = -1; + if (!("props" in cache)) { + cache.props = new Dictionary(); + } else if (!(cache.props instanceof Dictionary)) { + cache.props = Dictionary.fromObject(cache.props); + } +} + +function to_json(cache) { + return { + cname: cache.cname, + props: cache.props.toObject() + }; +} + function minify(files, options) { var warn_function = AST_Node.warn_function; try { @@ -35,6 +52,7 @@ function minify(files, options) { ie8: false, keep_fnames: false, mangle: {}, + nameCache: null, output: {}, parse: {}, sourceMap: false, @@ -52,7 +70,7 @@ function minify(files, options) { set_shorthand("warnings", options, [ "compress" ]); if (options.mangle) { options.mangle = defaults(options.mangle, { - cache: null, + cache: options.nameCache && (options.nameCache.vars || {}), eval: false, ie8: false, keep_fnames: false, @@ -60,6 +78,16 @@ function minify(files, options) { reserved: [], toplevel: false, }, true); + if (options.nameCache && options.mangle.properties) { + if (typeof options.mangle.properties != "object") { + options.mangle.properties = {}; + } + if (!("cache" in options.mangle.properties)) { + options.mangle.properties.cache = options.nameCache.props || {}; + } + } + init_cache(options.mangle.cache); + init_cache(options.mangle.properties.cache); } if (options.sourceMap) { options.sourceMap = defaults(options.sourceMap, { @@ -153,6 +181,12 @@ function minify(files, options) { } } } + if (options.nameCache && options.mangle) { + if (options.mangle.cache) options.nameCache.vars = to_json(options.mangle.cache); + if (options.mangle.properties && options.mangle.properties.cache) { + options.nameCache.props = to_json(options.mangle.properties.cache); + } + } if (timings) { timings.end = Date.now(); result.timings = { diff --git a/test/mocha/minify.js b/test/mocha/minify.js index b4722ce2..88e9c4eb 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -1,6 +1,7 @@ var Uglify = require('../../'); var assert = require("assert"); var readFileSync = require("fs").readFileSync; +var run_code = require("../sandbox").run_code; function read(path) { return readFileSync(path, "utf8"); @@ -20,6 +21,58 @@ describe("minify", function() { assert.strictEqual(result.code, "alert(2);"); }); + it("Should work with mangle.cache", function() { + var cache = {}; + var original = ""; + var compressed = ""; + [ + "bar.es5", + "baz.es5", + "foo.es5", + "qux.js", + ].forEach(function(file) { + var code = read("test/input/issue-1242/" + file); + var result = Uglify.minify(code, { + mangle: { + cache: cache, + toplevel: true + } + }); + if (result.error) throw result.error; + original += code; + compressed += result.code; + }); + assert.strictEqual(JSON.stringify(cache).slice(0, 20), '{"cname":5,"props":{'); + assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);'); + assert.strictEqual(run_code(compressed), run_code(original)); + }); + + it("Should work with nameCache", function() { + var cache = {}; + var original = ""; + var compressed = ""; + [ + "bar.es5", + "baz.es5", + "foo.es5", + "qux.js", + ].forEach(function(file) { + var code = read("test/input/issue-1242/" + file); + var result = Uglify.minify(code, { + mangle: { + toplevel: true + }, + nameCache: cache + }); + if (result.error) throw result.error; + original += code; + compressed += result.code; + }); + assert.strictEqual(JSON.stringify(cache).slice(0, 28), '{"vars":{"cname":5,"props":{'); + assert.strictEqual(compressed, 'function n(n){return 3*n}function r(n){return n/2}function c(o){l("Foo:",2*o)}var l=console.log.bind(console);var f=n(3),i=r(12);l("qux",f,i),c(11);'); + assert.strictEqual(run_code(compressed), run_code(original)); + }); + describe("keep_quoted_props", function() { it("Should preserve quotes in object literals", function() { var js = 'var foo = {"x": 1, y: 2, \'z\': 3};';