Skip to content

Commit

Permalink
feat(uglify): add support for UglifyJS3
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The "preserveComments" option has been removed.

As a replacement, set the `options.output.comments` option directly,
UglifyJS3 understands the following options:

- "all", to attempt to keep all comments.
- "some", to keep comments containing some license text.
- a RegExp
- a function that should return true or false.

While the "some" option works for many cases, it doesn't fully match the
behavior of the "license" option. Fortunately, you can pass the exported
function from `uglify-save-license` as the comment option.

Fixes #284.
Fixes #265.
  • Loading branch information
terinjokes committed May 16, 2017
1 parent ad73ab8 commit 4cc8751
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 223 deletions.
66 changes: 11 additions & 55 deletions README.md
Expand Up @@ -32,59 +32,15 @@ information, see [Why Use Pump?](docs/why-use-pump/README.md#why-use-pump).

## Options

- `mangle`
Most of the [minify options](https://github.com/mishoo/UglifyJS2#minify-options) from
the UglifyJS API are supported. There are a few exceptions:

Pass `false` to skip mangling names.
1. The `sourceMap` option must not be set, as it will be automatically configured
based on your Gulp configuration. See the documentation for [Gulp sourcemaps][gulp-sm].
2. Setting the `warnings` option has no visible affect. [See #163](gh-163).

- `output`

Pass an object if you wish to specify additional [output
options](http://lisperator.net/uglifyjs/codegen). The defaults are
optimized for best compression.

- `compress`

Pass an object to specify custom [compressor
options](http://lisperator.net/uglifyjs/compress). Pass `false` to skip
compression completely.

- `preserveComments`

A convenience option for `options.output.comments`. Defaults to preserving no
comments.

- `all`

Preserve all comments in code blocks

- `license`

Attempts to preserve comments that likely contain licensing information,
even if the comment does not have directives such as `@license` or `/*!`.

Implemented via the [`uglify-save-license`](https://github.com/shinnn/uglify-save-license)
module, this option preserves a comment if one of the following is true:

1. The comment is in the *first* line of a file
2. A regular expression matches the string of the comment.
For example: `MIT`, `@license`, or `Copyright`.
3. There is a comment at the *previous* line, and it matches 1, 2, or 3.

- `function`

Specify your own comment preservation function. You will be passed the
current node and the current comment and are expected to return either
`true` or `false`.

- `some` (deprecated)

Preserve comments that start with a bang (`!`) or include a Closure Compiler
directive (`@preserve`, `@license`, `@cc_on`).
Deprecated in favor of the `license` option, documented above.

You can also pass the `uglify` function any of the options [listed
here](https://github.com/mishoo/UglifyJS2#the-simple-way) to modify
UglifyJS's behavior.
[gulp-sm]: https://github.com/gulp-sourcemaps/gulp-sourcemaps#usage
[gh-163]: https://github.com/terinjokes/gulp-uglify/issues/163

## Errors

Expand All @@ -105,20 +61,20 @@ To see useful error messages, see [Why Use Pump?](docs/why-use-pump/README.md#wh

## Using a Different UglifyJS

**This API may change before the v3 release. Consider it deprecated.**

By default, `gulp-uglify` uses the version of UglifyJS installed as a dependency.
It's possible to configure the use of a different version using the "minifier" entry point.

```javascript
var uglifyjs = require('uglify-js'); // can be a git checkout
// or another module (such as `uglify-js-harmony` for ES6 support)
// or another module (such as `uglify-es` for ES6 support)
var minifier = require('gulp-uglify/minifier');
var pump = require('pump');

gulp.task('compress', function (cb) {
// the same options as described above
var options = {
preserveComments: 'license'
};
var options = {};

pump([
gulp.src('lib/*.js'),
Expand Down
63 changes: 15 additions & 48 deletions minifier.js
@@ -1,32 +1,15 @@
'use strict';
var through = require('through2');
var applySourceMap = require('vinyl-sourcemaps-apply');
var saveLicense = require('uglify-save-license');
var isObject = require('lodash/fp/isObject');
var zipObject = require('lodash/fp/zipObject');
var map = require('lodash/fp/map');
var prop = require('lodash/fp/prop');
var _ = require('lodash/fp/placeholder');
var defaultsDeep = require('lodash/fp/defaultsDeep');
var log = require('./lib/log');
var createError = require('./lib/create-error');
var GulpUglifyError = require('./lib/gulp-uglify-error');

var reSourceMapComment = /\n\/\/# sourceMappingURL=.+?$/;

var defaultOptions = defaultsDeep({
fromString: true,
output: {}
});

function trycatch(fn, handle) {
try {
return fn();
} catch (err) {
return handle(err);
}
}

function setup(opts) {
if (opts && !isObject(opts)) {
log.warn('gulp-uglify expects an object, non-object provided');
Expand All @@ -35,24 +18,12 @@ function setup(opts) {

var options = defaultOptions(opts);

if (options.preserveComments === 'all') {
options.output.comments = true;
} else if (options.preserveComments === 'some') {
// Preserve comments with directives or that start with a bang (!)
options.output.comments = /^!|@preserve|@license|@cc_on/i;
} else if (options.preserveComments === 'license') {
options.output.comments = saveLicense;
} else if (typeof options.preserveComments === 'function') {
options.output.comments = options.preserveComments;
}

return options;
}

module.exports = function(opts, uglify) {
function minify(file, encoding, callback) {
var options = setup(opts || {});
var sources;

if (file.isNull()) {
return callback(null, file);
Expand All @@ -63,37 +34,33 @@ module.exports = function(opts, uglify) {
}

if (file.sourceMap) {
options.sourceMap = {
filename: file.sourceMap.file,
includeSources: true
};

// UglifyJS generates broken source maps if the input source map
// does not contain mappings.
if (file.sourceMap.mappings) {
options.inSourceMap = file.sourceMap;
options.sourceMap.content = file.sourceMap;
}
options.outSourceMap = file.relative;

sources = zipObject(
file.sourceMap.sources,
file.sourceMap.sourcesContent
);
}

var mangled = trycatch(function() {
var map = {};
map[file.relative] = String(file.contents);
var m = uglify.minify(map, options);
m.code = new Buffer(m.code.replace(reSourceMapComment, ''));
return m;
}, createError(file, 'unable to minify JavaScript'));
var fileMap = {};
fileMap[file.relative] = String(file.contents);

if (mangled instanceof GulpUglifyError) {
return callback(mangled);
var mangled = uglify.minify(fileMap, options);

if (mangled.error) {
return callback(
createError(file, 'unable to minify JavaScript', mangled.error)
);
}

file.contents = mangled.code;
file.contents = new Buffer(mangled.code);

if (file.sourceMap) {
var sourceMap = JSON.parse(mangled.map);

sourceMap.sourcesContent = map(prop(_, sources), sourceMap.sources);
applySourceMap(file, sourceMap);
}

Expand Down
9 changes: 4 additions & 5 deletions package.json
@@ -1,7 +1,7 @@
{
"name": "gulp-uglify",
"description": "Minify files with UglifyJS.",
"version": "2.1.2",
"version": "3.0.0",
"author": "Terin Stock <terinjokes@gmail.com>",
"bugs": "https://github.com/terinjokes/gulp-uglify/issues",
"dependencies": {
Expand All @@ -10,14 +10,13 @@
"lodash": "^4.13.1",
"make-error-cause": "^1.1.1",
"through2": "^2.0.0",
"uglify-js": "~2.8.10",
"uglify-save-license": "^0.4.1",
"uglify-js": "^3.0.5",
"vinyl-sourcemaps-apply": "^0.2.0"
},
"devDependencies": {
"coveralls": "^2.11.4",
"eslint": "^3.18.0",
"eslint-config-prettier": "^1.5.0",
"eslint-config-prettier": "^2.1.0",
"eslint-config-xo": "^0.18.1",
"eslint-plugin-no-use-extend-native": "^0.3.12",
"eslint-plugin-prettier": "^2.0.1",
Expand All @@ -28,8 +27,8 @@
"istanbul": "^0.4.0",
"mississippi": "^1.2.0",
"mocha": "^3.0.1",
"prettier": "^1.1.0",
"power-assert": "^1.4.1",
"prettier": "^1.1.0",
"semver": "^5.3.0",
"tape": "^4.0.0",
"testdouble": "^2.1.2",
Expand Down
99 changes: 0 additions & 99 deletions test/comments.js

This file was deleted.

1 change: 0 additions & 1 deletion test/injectable.js
Expand Up @@ -34,7 +34,6 @@ describe('injecting mocha', function() {
},
{
injecting: true,
fromString: true,
output: {}
}
)
Expand Down
4 changes: 1 addition & 3 deletions test/minify.js
Expand Up @@ -19,9 +19,7 @@ var beforeEach = mocha.beforeEach;
describe('minify', function() {
var testContentsInput =
'"use strict"; (function(console, first, second) { console.log(first + second) }(5, 10))';
var testContentsExpected = uglifyjs.minify(testContentsInput, {
fromString: true
}).code;
var testContentsExpected = uglifyjs.minify(testContentsInput).code;

beforeEach(function() {
this.log = td.replace('../lib/log');
Expand Down
1 change: 0 additions & 1 deletion test/no-compress.js
Expand Up @@ -18,7 +18,6 @@ describe('no-compress', function() {
var testContentsInput =
'"use strict"; (function(console, first, second) {\n\tconsole.log(first + second)\n}(5, 10))';
var testContentsExpected = uglifyjs.minify(testContentsInput, {
fromString: true,
compress: false
}).code;

Expand Down
15 changes: 4 additions & 11 deletions test/sourcemap.js
Expand Up @@ -19,16 +19,11 @@ var beforeEach = mocha.beforeEach;
describe('source maps', function() {
var testContents1Input =
'(function(first, second) {\n console.log(first + second);\n}(5, 10));\n';
var testContents1Expected = uglifyjs.minify(testContents1Input, {
fromString: true
}).code;
var testContents1Expected = uglifyjs.minify(testContents1Input).code;
var testContents2Input = '(function(alert) {\n alert(5);\n}(alert));\n';
var testContents2Expected = uglifyjs.minify(testContents2Input, {
fromString: true
}).code;
var testContents2Expected = uglifyjs.minify(testContents2Input).code;
var testConcatExpected = uglifyjs.minify(
testContents1Input + '\n' + testContents2Input,
{fromString: true}
testContents1Input + '\n' + testContents2Input
).code;

beforeEach(function() {
Expand Down Expand Up @@ -222,9 +217,7 @@ describe('source maps', function() {
it('should avoid "ghost" files in sourcemaps', function(done) {
var testBabelInput =
'"use strict";\n\n(function (first, second) {\n console.log(first + second);\n})(5, 10);';
var testBabelExpected = uglifyjs.minify(testBabelInput, {
fromString: true
}).code;
var testBabelExpected = uglifyjs.minify(testBabelInput).code;

var testFile = new Vinyl({
cwd: '/home/terin/broken-promises/',
Expand Down

0 comments on commit 4cc8751

Please sign in to comment.