Skip to content

Commit

Permalink
improve usability of name cache under minify() (#2176)
Browse files Browse the repository at this point in the history
fixes #2174
  • Loading branch information
alexlamsl authored Jun 29, 2017
1 parent 5e6f264 commit bdeadff
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 28 deletions.
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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> 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.
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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,
}
Expand Down
27 changes: 2 additions & 25 deletions bin/uglifyjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
}
Expand Down
36 changes: 35 additions & 1 deletion lib/minify.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,6 +52,7 @@ function minify(files, options) {
ie8: false,
keep_fnames: false,
mangle: {},
nameCache: null,
output: {},
parse: {},
sourceMap: false,
Expand All @@ -52,14 +70,24 @@ 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,
properties: false,
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, {
Expand Down Expand Up @@ -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 = {
Expand Down
53 changes: 53 additions & 0 deletions test/mocha/minify.js
Original file line number Diff line number Diff line change
@@ -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");
Expand All @@ -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};';
Expand Down

0 comments on commit bdeadff

Please sign in to comment.