Skip to content

Commit

Permalink
feature: Allow UglifyJS instance to be injected [fixes #77]
Browse files Browse the repository at this point in the history
It's desirable to allow end users to inject their own version of
UglifyJS into the plugin code for two reasons:

1. Allow end users to update to newer versions of UglifyJS without
   waiting on this project to update, allowing them to easily test
   unreleased patches in the upstream project.
2. Allows us to more directly test our module's interface between gulp
   and UglifyJS, and to simplify our unit tests, since we no longer need
   to worry about the specific UglifyJS output.

This commit changes the implementation, allowing UglifyJS to be
injected, but does not change the tests to utilize this new feature.
This ensures we continue to pass our previous API contract.
  • Loading branch information
terinjokes committed Mar 18, 2015
1 parent fae2d18 commit 36ffb74
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 91 deletions.
94 changes: 4 additions & 90 deletions index.js
@@ -1,93 +1,7 @@
'use strict';
var through = require('through2'),
uglify = require('uglify-js'),
merge = require('deepmerge'),
PluginError = require('gulp-util/lib/PluginError'),
applySourceMap = require('vinyl-sourcemaps-apply'),
reSourceMapComment = /\n\/\/# sourceMappingURL=.+?$/,
pluginName = 'gulp-uglify';
var uglfiy = require('uglify-js');
var minifier = require('./minifier');

function minify(file, options) {
var mangled;

try {
mangled = uglify.minify(String(file.contents), options);
mangled.code = new Buffer(mangled.code.replace(reSourceMapComment, ''));
return mangled;
} catch (e) {
return createError(file, e);
}
}

function setup(opts) {
var options = merge(opts || {}, {
fromString: true,
output: {}
});

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 (typeof options.preserveComments === 'function') {
options.output.comments = options.preserveComments;
}

return options;
}

function createError(file, err) {
if (typeof err === 'string') {
return new PluginError(pluginName, file.path + ': ' + err, {
fileName: file.path,
showStack: false
});
}

var msg = err.message || err.msg || /* istanbul ignore next */ 'unspecified error';

return new PluginError(pluginName, file.path + ': ' + msg, {
fileName: file.path,
lineNumber: err.line,
stack: err.stack,
showStack: false
});
}

module.exports = function(opt) {

function uglify(file, encoding, callback) {
var options = setup(opt);

if (file.isNull()) {
return callback(null, file);
}

if (file.isStream()) {
return callback(createError(file, 'Streaming not supported'));
}

if (file.sourceMap) {
options.outSourceMap = file.relative;
}

var mangled = minify(file, options);

if (mangled instanceof PluginError) {
return callback(mangled);
}

file.contents = mangled.code;

if (file.sourceMap) {
var sourceMap = JSON.parse(mangled.map);
sourceMap.sources = [file.relative];
applySourceMap(file, sourceMap);
}

callback(null, file);
}

return through.obj(uglify);
module.exports = function(opts) {
return minifier(opts, uglfiy);
};
91 changes: 91 additions & 0 deletions minifier.js
@@ -0,0 +1,91 @@
'use strict';
var through = require('through2');
var deap = require('deap');
var PluginError = require('gulp-util/lib/PluginError');
var applySourceMap = require('vinyl-sourcemaps-apply');
var reSourceMapComment = /\n\/\/# sourceMappingURL=.+?$/;
var pluginName = 'gulp-uglify';

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

function setup(opts) {
var options = deap({}, opts, {
fromString: true,
output: {}
});

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 (typeof options.preserveComments === 'function') {
options.output.comments = options.preserveComments;
}

return options;
}

function createError(file, err) {
if (typeof err === 'string') {
return new PluginError(pluginName, file.path + ': ' + err, {
fileName: file.path,
showStack: false
});
}

var msg = err.message || err.msg || /* istanbul ignore next */ 'unspecified error';

return new PluginError(pluginName, file.path + ': ' + msg, {
fileName: file.path,
lineNumber: err.line,
stack: err.stack,
showStack: false
});
}

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

if (file.isNull()) {
return callback(null, file);
}

if (file.isStream()) {
return callback(createError(file, 'Streaming not supported'));
}

if (file.sourceMap) {
options.outSourceMap = file.relative;
}

var mangled = trycatch(function() {
var m = uglify.minify(String(file.contents), options);
m.code = new Buffer(m.code.replace(reSourceMapComment, ''));
return m;
}, createError.bind(null, file));

if (mangled instanceof PluginError) {
return callback(mangled);
}

file.contents = mangled.code;

if (file.sourceMap) {
var sourceMap = JSON.parse(mangled.map);
sourceMap.sources = [file.relative];
applySourceMap(file, sourceMap);
}

callback(null, file);
}

return through.obj(minify);
};
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -5,7 +5,7 @@
"author": "Terin Stock <terinjokes@gmail.com>",
"bugs": "https://github.com/terinjokes/gulp-uglify/issues",
"dependencies": {
"deepmerge": ">=0.2.7 <0.3.0-0",
"deap": ">=1.0.0 <2.0.0-0",
"gulp-util": ">=3.0.0 <4.0.0-0",
"through2": ">=0.6.1 <1.0.0-0",
"uglify-js": "2.4.16",
Expand All @@ -14,6 +14,7 @@
"devDependencies": {
"argg": "0.0.1",
"codeclimate-test-reporter": "0.0.3",
"cmem": ">=1.0.0 <2.0.0-0",
"gulp-concat": ">=2.3.4 <3.0.0-0",
"gulp-sourcemaps": ">=1.1.1 <2.0.0-0",
"istanbul": ">=0.3.0 <0.4.0-0",
Expand Down
50 changes: 50 additions & 0 deletions test/injectable.js
@@ -0,0 +1,50 @@
'use strict';
var test = require('tape');
var Vinyl = require('vinyl');
var minifer = require('../minifier');
var cmem = require('cmem');

var testContentsOutput = 'function abs(a, b) {\n return a > b; }';
var testContentsInput = 'function testInput() {}';
var testFile = new Vinyl({
cwd: '/home/terin/broken-promises/',
base: '/home/terin/broken-promises/test',
path: '/home/terin/broken-promises/test/test1.js',
contents: new Buffer(testContentsInput)
});
var uglifyjs = {
minify: cmem(function() {
return {
code: testContentsOutput
};
})
};

test('should minify files', function(t) {
t.plan(10);

var stream = minifer({injecting: true}, uglifyjs);

stream.on('data', function(newFile) {
t.ok(newFile, 'emits a file');
t.ok(newFile.path, 'file has a path');
t.ok(newFile.relative, 'file has relative path information');
t.ok(newFile.contents, 'file has contents');

t.ok(newFile instanceof Vinyl, 'file is Vinyl');
t.ok(newFile.contents instanceof Buffer, 'file contents are a buffer');

t.equals(String(newFile.contents), testContentsOutput);

t.equals(uglifyjs.minify.$count, 1, 'minify stub was called only once');
t.equals(uglifyjs.minify.$args[0], testContentsInput, 'stub argument 0 was the expected input');
t.deepEqual(uglifyjs.minify.$args[1], {
fromString: true,
output: {},
injecting: true
}, 'stub argument 1 was the expected options');
});

stream.write(testFile);
stream.end();
});

0 comments on commit 36ffb74

Please sign in to comment.