Skip to content

Commit

Permalink
Updated grunt wrapper to be able to hook into uglify's new object pro…
Browse files Browse the repository at this point in the history
…perty name mangling functionality, the ability to pass files containing property and variable name exceptions and the ability to use a cache to coordinate symbol mangling across multiple calls to uglify.

See uglifyjs v2.4.18 https://www.npmjs.com/package/uglify-js for more info.
  • Loading branch information
jrhite committed Apr 2, 2015
1 parent eaa82c5 commit 809fb2e
Show file tree
Hide file tree
Showing 18 changed files with 256 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
@@ -1,7 +1,7 @@
root = true

[*]
indent_style = spaces
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
Expand Down
63 changes: 62 additions & 1 deletion Gruntfile.js
Expand Up @@ -269,14 +269,70 @@ module.exports = function(grunt) {
mangle: false,
compress: false
}
},
mangleprops: {
files: {
'tmp/mangleprops.js': ['test/fixtures/src/mangleprops.js']
},
options: {
mangleProperties: true
}
},
mangleprops_withExcept: {
files: {
'tmp/mangleprops_withExcept.js': ['test/fixtures/src/mangleprops.js']
},
options: {
mangle: {
except: ['dontMangleMeVariable']
},
mangleProperties: true
}
},
mangleprops_withExceptionsFiles: {
files: {
'tmp/mangleprops_withExceptionsFiles.js': ['test/fixtures/src/mangleprops.js']
},
options: {
mangle: {
toplevel: true
},
mangleProperties: true,
exceptionsFiles: ['test/fixtures/src/exceptionsfile1.json', 'test/fixtures/src/exceptionsfile2.json']
}
},
mangleprops_withExceptAndExceptionsFiles: {
files: {
'tmp/mangleprops_withExceptAndExceptionsFiles.js': ['test/fixtures/src/mangleprops.js']
},
options: {
mangle: {
toplevel: true,
except: ['dontMangleMeVariable']
},
mangleProperties: true,
exceptionsFiles: ['test/fixtures/src/exceptionsfile1.json', 'test/fixtures/src/exceptionsfile2.json']
}
},
mangleprops_withNameCacheFile: {
files: {
'tmp/mangleprops_withNameCacheFile1.js': ['test/fixtures/src/mangleprops.js'],
'tmp/mangleprops_withNameCacheFile2.js': ['test/fixtures/src/mangleprops_withNameCache.js']
},
options: {
mangle: {
toplevel: true
},
mangleProperties: true,
nameCache: 'tmp/uglify_name_cache.json'
}
}
},

// Unit tests.
nodeunit: {
tests: ['test/*_test.js']
}

});

// task that expects its argument (another task) to fail
Expand Down Expand Up @@ -339,6 +395,11 @@ module.exports = function(grunt) {
'uglify:sourcemapin_sources',
'uglify:expression_json',
'uglify:expression_js',
'uglify:mangleprops',
'uglify:mangleprops_withExcept',
'uglify:mangleprops_withExceptionsFiles',
'uglify:mangleprops_withExceptAndExceptionsFiles',
'uglify:mangleprops_withNameCacheFile',
'nodeunit'
]);

Expand Down
50 changes: 50 additions & 0 deletions docs/uglify-examples.md
Expand Up @@ -237,3 +237,53 @@ grunt.initConfig({
}
});
```

## Turn on object property name mangling

This configuration will turn on object property name mangling, but not mangle built-in browser object properties.
Additionally, variables and object properties listed in the `myExceptionsFile.json` will be mangled. For more info,
on the format of the exception file format please see the [UglifyJS docs](https://www.npmjs.com/package/uglify-js).

```js
// Project configuration.
grunt.initConfig({
uglify: {
options: {
mangleProperties: true,
reserveDOMCache: true,
exceptionsFiles: [ 'myExceptionsFile.json' ]
},
my_target: {
files: {
'dest/output.min.js': ['src/input.js']
}
}
}
});
```

## Turn on use of name mangling cache

Turn on use of name mangling cache to coordinate mangled symbols between outputted uglify files. uglify will the
generate a JSON cache file with the name provided in the options. Note: this generated file uses the same JSON format
as the `exceptionsFiles` files.

```js
// Project configuration.
grunt.initConfig({
uglify: {
options: {
nameCache: '.tmp/grunt-uglify-cache.json',
},
my_target: {
files: {
'dest/output1.min.js': ['src/input1.js'],
'dest/output2.min.js': ['src/input2.js']
}
}
}
});
```



40 changes: 34 additions & 6 deletions docs/uglify-options.md
Expand Up @@ -31,7 +31,7 @@ Turns on beautification of the generated source code. An `Object` will be merged


#### expression
Type: `Boolean`
Type: `Boolean`
Default: `false`

Parse a single expression, rather than a program (for parsing JSON)
Expand Down Expand Up @@ -64,13 +64,13 @@ uglify source is passed as the argument and the return value will be used as the
when there's one source file.

## sourceMapIncludeSources
Type: `Boolean`
Type: `Boolean`
Default: `false`

Pass this flag if you want to include the content of source files in the source map as sourcesContent property.

#### sourceMapRoot
Type: `String`
Type: `String`
Default: `undefined`

With this option you can customize root URL that browser will use when looking for sources.
Expand All @@ -93,13 +93,13 @@ For variables that need to be public `exports` and `global` variables are made a
The value of wrap is the global variable exports will be available as.

## maxLineLen
Type: `Number`
Type: `Number`
Default: `32000`

Limit the line length in symbols. Pass maxLineLen = 0 to disable this safety feature.

## ASCIIOnly
Type: `Boolean`
Type: `Boolean`
Default: `false`

Enables to encode non-ASCII characters as \uXXXX.
Expand Down Expand Up @@ -135,7 +135,35 @@ Default: empty string
This string will be appended to the minified output. Template strings (e.g. `<%= config.value %>` will be expanded automatically.

## screwIE8
Type: `Boolean`
Type: `Boolean`
Default: false

Pass this flag if you don't care about full compliance with Internet Explorer 6-8 quirks.

## mangleProperties
Type: `Boolean`
Default: false

Use this flag to turn on object property name mangling.

## reserveDOMProperties
Type: `Boolean`
Default: false

Use this flag in conjunction with `mangleProperties` to prevent built-in browser object properties from being mangled.

## exceptionsFiles
Type: `Array`
Default: []

Use this with `mangleProperties` to pass one or more JSON files containing a list of variables and object properties
that should not be mangled. See the [UglifyJS docs](https://www.npmjs.com/package/uglify-js) for more info on the file syntax.

## nameCache
Type: `String`
Default: empty string

A string that is a path to a JSON cache file that uglify will create and use to coordinate symbol mangling between
multiple runs of uglify. Note: this generated file uses the same JSON format as the `exceptionsFiles` files.


2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -23,7 +23,7 @@
"chalk": "^1.0.0",
"lodash": "^3.2.0",
"maxmin": "^1.0.0",
"uglify-js": "2.4.17",
"uglify-js": "^2.4.19",
"uri-path": "0.0.2"
},
"devDependencies": {
Expand Down
58 changes: 56 additions & 2 deletions tasks/lib/uglify.js
Expand Up @@ -70,10 +70,15 @@ exports.init = function(grunt) {
topLevel = topLevel.wrap_enclose(argParamList);
}

var topLevelCache = null;
if (options.nameCache) {
topLevelCache = UglifyJS.readNameCache(options.nameCache, 'vars');
}

// Need to call this before we mangle or compress,
// and call after any compression or ast altering
if (options.expression === false) {
topLevel.figure_out_scope({screw_ie8: options.screwIE8});
topLevel.figure_out_scope({ screw_ie8: options.screwIE8, cache: topLevelCache });
}

if (options.compress !== false) {
Expand All @@ -87,7 +92,45 @@ exports.init = function(grunt) {
topLevel = topLevel.transform(compressor);

// Need to figure out scope again after source being altered
topLevel.figure_out_scope({screw_ie8: options.screwIE8});
if (options.expression === false) {
topLevel.figure_out_scope({screw_ie8: options.screwIE8, cache: topLevelCache});
}
}

var mangleExclusions = { vars: [], props: [] };
if (options.reserveDOMProperties) {
mangleExclusions = UglifyJS.readDefaultReservedFile();
}

if (options.exceptionsFiles) {
try {
options.exceptionsFiles.forEach(function(filename) {
mangleExclusions = UglifyJS.readReservedFile(filename, mangleExclusions);
});
} catch (ex) {
grunt.warn(ex);
}
}

var cache = null;
if (options.nameCache) {
cache = UglifyJS.readNameCache(options.nameCache, 'props');
}

if (options.mangleProperties === true) {
topLevel = UglifyJS.mangle_properties(topLevel, {
reserved: mangleExclusions ? mangleExclusions.props : null,
cache: cache
});

if (options.nameCache) {
UglifyJS.writeNameCache(options.nameCache, 'props', cache);
}

// Need to figure out scope again since topLevel has been altered
if (options.expression === false) {
topLevel.figure_out_scope({screw_ie8: options.screwIE8, cache: topLevelCache});
}
}

if (options.mangle !== false) {
Expand All @@ -97,9 +140,20 @@ exports.init = function(grunt) {
// // compute_char_frequency optimizes names for compression
// topLevel.compute_char_frequency(options.mangle);

options.mangle.cache = topLevelCache;

options.mangle.except = options.mangle.except ? options.mangle.except : [];
if (mangleExclusions.vars) {
mangleExclusions.vars.forEach(function(name){
UglifyJS.push_uniq(options.mangle.except, name);
});
}

// Requires previous call to figure_out_scope
// and should always be called after compressor transform
topLevel.mangle_names(options.mangle);

UglifyJS.writeNameCache(options.nameCache, 'vars', options.mangle.cache);
}

if (options.sourceMap && options.sourceMapIncludeSources) {
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/expected/mangleprops.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/expected/mangleprops_withExcept.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/expected/mangleprops_withExceptionsFiles.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixtures/expected/mangleprops_withNameCacheFile1.js
@@ -0,0 +1 @@
var a={a:function(a){a++},b:function(){},c:function(){}},b=function(){},c=function(){};
1 change: 1 addition & 0 deletions test/fixtures/expected/mangleprops_withNameCacheFile2.js
@@ -0,0 +1 @@
var d=10;a.a(d);
19 changes: 19 additions & 0 deletions test/fixtures/expected/uglify_name_cache.json
@@ -0,0 +1,19 @@
{
"props": {
"cname": 2,
"props": {
"$myFunction": "a",
"$dontMangleMeProperty": "b",
"$dontMangleMeProperty2": "c"
}
},
"vars": {
"cname": 3,
"props": {
"$myObj": "a",
"$dontMangleMeVariable2": "b",
"$dontMangleMeVariable3": "c",
"$myNumber": "d"
}
}
}
4 changes: 4 additions & 0 deletions test/fixtures/src/exceptionsfile1.json
@@ -0,0 +1,4 @@
{
"vars": ["dontMangleMeVariable2"],
"props": ["dontMangleMeProperty"]
}
5 changes: 5 additions & 0 deletions test/fixtures/src/exceptionsfile2.json
@@ -0,0 +1,5 @@
{
"vars": ["dontMangleMeVariable3"],
"props": []
}

8 changes: 8 additions & 0 deletions test/fixtures/src/mangleprops.js
@@ -0,0 +1,8 @@
var myObj = {
myFunction: function(dontMangleMeVariable) { dontMangleMeVariable++; },
dontMangleMeProperty: function() {},
dontMangleMeProperty2: function() {}
};

var dontMangleMeVariable2 = function() {};
var dontMangleMeVariable3 = function() {};
2 changes: 2 additions & 0 deletions test/fixtures/src/mangleprops_withNameCache.js
@@ -0,0 +1,2 @@
var myNumber = 10;
myObj.myFunction(myNumber);
9 changes: 8 additions & 1 deletion test/uglify_test.js
Expand Up @@ -49,7 +49,14 @@ exports.contrib_uglify = {
'sourcemaps_multiple2_fnName.js',
'sourcemaps_multiple2_fnName.js.fn.map',
'expression.json',
'expression.js'
'expression.js',
'mangleprops.js',
'mangleprops_withExcept.js',
'mangleprops_withExceptionsFiles.js',
'mangleprops_withExceptAndExceptionsFiles.js',
'mangleprops_withNameCacheFile1.js',
'mangleprops_withNameCacheFile2.js',
'uglify_name_cache.json'
];

test.expect(files.length);
Expand Down

0 comments on commit 809fb2e

Please sign in to comment.