diff --git a/README.md b/README.md index 3880e0c..aee8184 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ app.use(minify({ json_match: /json/, uglifyJS: undefined, cssmin: undefined, - cache: false + cache: false, + onerror: undefined, })); ``` @@ -80,6 +81,10 @@ app.use(minify({ the directory for cache storage (must be writeable). Pass `false` to cache in the memory (not recommended). +- `onerror`: `Function` + + handle compiling error or minifying errors. You can determine what to respond when facing such errors. See [Customize onError behavior](#customize-onrrror-behavior) for details. + ## Per-Response Options - `res._skip` @@ -190,6 +195,54 @@ app.use(minify()); **Change since 0.1.6**: You need to manually install those modules to enable this feature. +## Customize onError behavior + +Errors thrown by CoffeeScript/LESS/SASS/Stylus module are compiling errors and errors thrown by UglifyJS/cssmin/JSON module are minifying errors. + +The default behavior is to return the error message for compiling errors and return the original content for minifying errors. + +You can change this behavior or get notified about the error by providing `onerror` in options: + +```javascript +var minify = require('express-minify'); + +/* + +err: the Error object +stage: "compile" or "minify" +assetType: The type of the source code, can be one of + minify.Minifier.TYPE_TEXT + minify.Minifier.TYPE_JS + minify.Minifier.TYPE_CSS + minify.Minifier.TYPE_SASS + minify.Minifier.TYPE_LESS + minify.Minifier.TYPE_STYLUS + minify.Minifier.TYPE_COFFEE + minify.Minifier.TYPE_JSON +minifyOptions: Minification options +body: Source code (string) +callback: (err, body) return the final result as string in this callback function. + If `err` is null, the final result will be cached. + +*/ + +var myErrorHandler = function (err, stage, assetType, minifyOptions, body, callback) { + console.log(err); + // below is the default implementation + if (stage === 'compile') { + callback(err, JSON.stringify(err)); + return; + } + callback(err, body); +}; + +app.use(minify({ + onerror: myErrorHandler +})); +``` + +You can also access the default implementation from `minify.Minifier.defaultErrorHandler`. + ## Customize UglifyJS/cssmin instance If you want to use your own UglifyJS/cssmin instance (for example, use a different branch to support ES6), you can pass them to the options. @@ -303,6 +356,11 @@ If you are using `cluster`, it is strongly recommended to enable file cache. The # Change log +0.2.0 + +- Support `onerror` +- Minimum required NodeJs version changed to `0.12.0` for `fs.access`. + 0.1.7 - Support customizing UglifyJS/cssmin instance diff --git a/cache.js b/cache.js new file mode 100644 index 0000000..95dff37 --- /dev/null +++ b/cache.js @@ -0,0 +1,71 @@ +var fs = require('fs'); +var path = require('path'); + +var FileCache = function (basePath) { + this.basePath = basePath; +}; + +FileCache.prototype.get = function (hash, callback) { + var destPath = this.basePath + hash; + fs.readFile(destPath, {encoding: 'utf8'}, function (err, data) { + if (err) { + callback(err); + return; + } + callback(null, data.toString()); + }); +}; + +FileCache.prototype.put = function (hash, minized, callback) { + var destPath = this.basePath + hash; + var tempPath = destPath + '.tmp'; + // fix issue #3 + fs.writeFile(tempPath, minized, {encoding: 'utf8'}, function (err) { + if (err) { + callback(err); + return; + } + fs.rename(tempPath, destPath, callback); + }); +}; + +var MemoryCache = function () { + this.cache = {}; +}; + +MemoryCache.prototype.get = function (hash, callback) { + if (!this.cache.hasOwnProperty(hash)) { + callback(new Error('miss')); + } else { + callback(null, this.cache[hash]); + } +}; + +MemoryCache.prototype.put = function (hash, minized, callback) { + this.cache[hash] = minized; + callback(); +}; + +/** + * @param {String|false} cacheDirectory false == use memory cache + */ +var Cache = function (cacheDirectory) { + this.isFileCache = (cacheDirectory !== false); + if (this.isFileCache) { + // whether the directory is writeable + cacheDirectory = path.normalize(cacheDirectory + '/').toString(); + try { + fs.accessSync(cacheDirectory, fs.W_OK); + } catch (ignore) { + console.log('WARNING: express-minify cache directory is not writeable, fallback to memory cache.'); + this.isFileCache = false; + } + } + if (this.isFileCache) { + this.layer = new FileCache(cacheDirectory); + } else { + this.layer = new MemoryCache(); + } +}; + +module.exports = Cache; diff --git a/index.js b/index.js new file mode 100755 index 0000000..aa199ec --- /dev/null +++ b/index.js @@ -0,0 +1,187 @@ +var crypto = require('crypto'); +var onHeaders = require('on-headers'); + +var Factory = function (options) { + return createMiddleware(options); +}; + +Factory.Cache = require('./cache.js'); +Factory.Minifier = require('./minifier.js'); + +module.exports = Factory; + +var createMiddleware = function express_minify(options) { + options = options || {}; + + var js_match = options.js_match || /javascript/; + var css_match = options.css_match || /css/; + var sass_match = options.sass_match || /scss/; + var less_match = options.less_match || /less/; + var stylus_match = options.stylus_match || /stylus/; + var coffee_match = options.coffee_match || /coffeescript/; + var json_match = options.json_match || /json/; + + var cache = new Factory.Cache(options.cache || false); + var minifier = new Factory.Minifier(options.uglifyJS, options.cssmin, options.onerror); + + return function express_minify_middleware(req, res, next) { + var write = res.write; + var end = res.end; + + var buf = null; + var type = Factory.Minifier.TYPE_TEXT; + + onHeaders(res, function () { + if (req.method === 'HEAD') { + return; + } + + if (res._skip) { + return; + } + + var contentType = res.getHeader('Content-Type'); + if (contentType === undefined) { + return; + } + + // for sass, less, stylus, coffee module: + // null: module is found but not loaded + // false: module not found + // so we should not process false values, but allow null values + if (minifier.sass !== false && sass_match.test(contentType)) { + type = Factory.Minifier.TYPE_SASS; + res.setHeader('Content-Type', 'text/css'); + } else if (minifier.less !== false && less_match.test(contentType)) { + type = Factory.Minifier.TYPE_LESS; + res.setHeader('Content-Type', 'text/css'); + } else if (minifier.stylus !== false && stylus_match.test(contentType)) { + type = Factory.Minifier.TYPE_STYLUS; + res.setHeader('Content-Type', 'text/css'); + } else if (minifier.coffee !== false && coffee_match.test(contentType)) { + type = Factory.Minifier.TYPE_COFFEE; + res.setHeader('Content-Type', 'text/javascript'); + } else if (json_match.test(contentType)) { + type = Factory.Minifier.TYPE_JSON; + } else if (js_match.test(contentType)) { + type = Factory.Minifier.TYPE_JS; + } else if (css_match.test(contentType)) { + type = Factory.Minifier.TYPE_CSS; + } + + if (type === Factory.Minifier.TYPE_TEXT) { + return; + } + + if ((type === Factory.Minifier.TYPE_JS || type === Factory.Minifier.TYPE_CSS) && res._no_minify) { + return; + } + + res.removeHeader('Content-Length'); + + // prepare the buffer + buf = []; + }); + + res.write = function (chunk, encoding) { + if (!this._header) { + this._implicitHeader(); + } + + if (buf === null) { + return write.call(this, chunk, encoding); + } + + if (!this._hasBody) { + return true; + } + + if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) { + throw new TypeError('first argument must be a string or Buffer'); + } + + if (chunk.length === 0) { + return true; + } + + // no chunked_encoding here + if (typeof chunk === 'string') { + chunk = new Buffer(chunk, encoding); + } + + buf.push(chunk); + }; + + res.end = function (data, encoding) { + if (this.finished) { + return false; + } + + if (!this._header) { + this._implicitHeader(); + } + + if (data && !this._hasBody) { + data = false; + } + + if (buf === null) { + return end.call(this, data, encoding); + } + + // TODO: implement hot-path optimization + if (data) { + this.write(data, encoding); + } + + var buffer = Buffer.concat(buf); + + // prepare uglify options + var uglifyOptions = {}; + if (this._no_mangle) { + uglifyOptions.mangle = false; + } + if (this._uglifyMangle !== undefined) { + uglifyOptions.mangle = this._uglifyMangle; + } + if (this._uglifyOutput !== undefined) { + uglifyOptions.output = this._uglifyOutput; + } + if (this._uglifyCompress !== undefined) { + uglifyOptions.compress = this._uglifyCompress; + } + + var minifyOptions = { + uglifyOpt: uglifyOptions, + noMinify: this._no_minify + }; + + var cacheKey = crypto.createHash('sha1').update(JSON.stringify(minifyOptions) + buffer).digest('hex').toString(); + var self = this; + + cache.layer.get(cacheKey, function (err, minized) { + if (err) { + // cache miss + minifier.compileAndMinify(type, minifyOptions, buffer.toString(encoding), function (err, minized) { + if (self._no_cache || err) { + // do not cache the response body + write.call(self, minized, 'utf8'); + end.call(self); + } else { + cache.layer.put(cacheKey, minized, function () { + write.call(self, minized, 'utf8'); + end.call(self); + }); + } + }); + } else { + // cache hit + write.call(self, minized, 'utf8'); + end.call(self); + } + }); + }; + + next(); + }; +}; diff --git a/minifier.js b/minifier.js new file mode 100644 index 0000000..504ed9d --- /dev/null +++ b/minifier.js @@ -0,0 +1,185 @@ +var extend = require('util')._extend; + +/** + * Test whether a module exists. + * null for exists, false for not-exists. + */ +var testModule = function (name) { + var module = null; + try { + require.resolve(name); + } catch (ignore) { + module = false; + } + return module; +}; + +var Minifier = function (uglifyJS, cssmin, errorHandler) { + this.handleError = errorHandler || Minifier.defaultErrorHandler; + this.uglifyJS = uglifyJS || require('uglify-js'); + this.cssmin = cssmin || require('cssmin'); + this.sass = testModule('node-sass'); + this.less = testModule('less'); + this.stylus = testModule('stylus'); + this.coffee = testModule('coffee-script'); +}; + +Minifier.TYPE_TEXT = 0; +Minifier.TYPE_JS = 1; +Minifier.TYPE_CSS = 2; +Minifier.TYPE_SASS = 3; +Minifier.TYPE_LESS = 4; +Minifier.TYPE_STYLUS = 5; +Minifier.TYPE_COFFEE = 6; +Minifier.TYPE_JSON = 7; + +Minifier.defaultErrorHandler = function (err, stage, assetType, minifyOptions, body, callback) { + if (stage === 'compile') { + callback(err, JSON.stringify(err)); + return; + } + callback(err, body); +}; + +Minifier.prototype.compileAndMinify = function (assetType, minifyOptions, body, callback) { + if (typeof callback !== 'function') { + return; + } + + var self = this; + var result, opt; + + switch (assetType) { + case Minifier.TYPE_JS: + result = body; + try { + if (!minifyOptions.noMinify) { + opt = extend({fromString: true}, minifyOptions.uglifyOpt); + result = self.uglifyJS.minify(result, opt).code; + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + break; + case Minifier.TYPE_CSS: + result = body; + try { + if (!minifyOptions.noMinify) { + result = self.cssmin(result); + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + break; + case Minifier.TYPE_SASS: + if (!self.sass) { + self.sass = require('node-sass'); + } + result = body; + try { + result = self.sass.renderSync({ + data: result + }).css.toString(); + } catch (err) { + self.handleError(err, 'compile', assetType, minifyOptions, result, callback); + return; + } + try { + if (!minifyOptions.noMinify) { + result = self.cssmin(result); + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + break; + case Minifier.TYPE_LESS: + if (!self.less) { + self.less = require('less'); + } + result = body; + self.less.render(result, function (err, output) { + if (err) { + self.handleError(err, 'compile', assetType, minifyOptions, result, callback); + return; + } + result = output.css; + try { + if (!minifyOptions.noMinify) { + result = self.cssmin(result); + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + }); + break; + case Minifier.TYPE_STYLUS: + if (!self.stylus) { + self.stylus = require('stylus'); + } + result = body; + self.stylus.render(result, function (err, css) { + if (err) { + self.handleError(err, 'compile', assetType, minifyOptions, result, callback); + return; + } + result = css; + try { + if (!minifyOptions.noMinify) { + result = self.cssmin(result); + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + }); + break; + case Minifier.TYPE_COFFEE: + if (!self.coffee) { + self.coffee = require('coffee-script'); + } + result = body; + try { + result = self.coffee.compile(result); + } catch (err) { + self.handleError(err, 'compile', assetType, minifyOptions, result, callback); + return; + } + try { + if (!minifyOptions.noMinify) { + opt = extend({fromString: true}, minifyOptions.uglifyOpt); + result = self.uglifyJS.minify(result, opt).code; + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + break; + case Minifier.TYPE_JSON: + result = body; + try { + if (!minifyOptions.noMinify) { + result = JSON.stringify(JSON.parse(result)); + } + } catch (err) { + self.handleError(err, 'minify', assetType, minifyOptions, result, callback); + return; + } + callback(null, result); + break; + default: + callback(null, body); + break; + } +}; + +module.exports = Minifier; diff --git a/minify.js b/minify.js deleted file mode 100755 index 67b2dca..0000000 --- a/minify.js +++ /dev/null @@ -1,436 +0,0 @@ -var fs = require('fs'); -var extend = require('util')._extend; -var path = require('path'); -var crypto = require('crypto'); -var onHeaders = require('on-headers'); - -// js minifier and css minifier -var uglifyjs = require('uglify-js'); -var cssmin = require('cssmin'); - -var sass = null; -try { - require.resolve('node-sass'); -} catch (ignore) { - sass = false; -} - -var less = null; -try { - require.resolve('less'); -} catch (ignore) { - less = false; -} - -var stylus = null; -try { - require.resolve('stylus'); -} catch (ignore) { - stylus = false; -} - -var coffee = null; -try { - require.resolve('coffee-script'); -} catch (ignore) { - coffee = false; -} - -var memCache = {}; - -var TYPE_TEXT = 0; -var TYPE_JS = 1; -var TYPE_CSS = 2; -var TYPE_SASS = 3; -var TYPE_LESS = 4; -var TYPE_STYLUS = 5; -var TYPE_COFFEE = 6; -var TYPE_JSON = 7; - -function precompileError(err, assetType) { - return JSON.stringify(err); -} - -function minifyIt(assetType, options, minifiers, content, callback) { - if (typeof callback !== 'function') { - return; - } - - var result, opt; - - switch (assetType) { - case TYPE_JS: - result = content; - try { - if (!options.noMinify) { - opt = extend({fromString: true}, options.uglifyOpt); - result = minifiers.uglifyJS.minify(result, opt).code; - } - } catch (ignore) { - } - callback(result); - break; - case TYPE_CSS: - result = content; - try { - if (!options.noMinify) { - result = minifiers.cssmin(content); - } - } catch (ignore) { - } - callback(result); - break; - case TYPE_SASS: - if (!sass) { - sass = require('node-sass'); - } - try { - result = sass.renderSync({ - data: content - }).css.toString(); - try { - if (!options.noMinify) { - result = minifiers.cssmin(result); - } - } catch (ignore) { - } - } catch (err) { - result = precompileError(err, assetType); - } - callback(result); - break; - case TYPE_LESS: - if (!less) { - less = require('less'); - } - less.render(content, function (err, output) { - if (err) { - callback(precompileError(err, assetType)); - return; - } - result = output.css; - try { - if (!options.noMinify) { - result = minifiers.cssmin(result); - } - } catch (ignore) { - } - callback(result); - }); - break; - case TYPE_STYLUS: - if (!stylus) { - stylus = require('stylus'); - } - stylus.render(content, function (err, css) { - if (err) { - callback(precompileError(err, assetType)); - return; - } - result = css; - try { - if (!options.noMinify) { - result = minifiers.cssmin(result); - } - } catch (ignore) { - } - callback(result); - }); - break; - case TYPE_COFFEE: - if (!coffee) { - coffee = require('coffee-script'); - } - try { - result = coffee.compile(content); - try { - if (!options.noMinify) { - opt = extend({fromString: true}, options.uglifyOpt); - result = minifiers.uglifyJS.minify(result, opt).code; - } - } catch (ignore) { - } - } catch (err) { - result = precompileError(err, assetType); - } - callback(result); - break; - case TYPE_JSON: - result = content; - try { - if (!options.noMinify) { - result = JSON.stringify(JSON.parse(content)); - } - } catch (ignore) { - } - callback(result); - break; - default: - callback(content); - break; - } -} - -function cacheGetFile(hash, callback) { - if (typeof callback !== 'function') { - return; - } - - var filepath = this.toString(); - - fs.readFile(filepath + hash, { encoding: 'utf8' }, function (err, data) { - if (err) { - callback(err); - return; - } - try { - data = JSON.parse(data).content; - callback(null, data); - } catch (jsonErr) { - callback(jsonErr); - } - }); -} - -function cachePutFile(hash, minized, callback) { - var filepath = this.toString(); - - // fix issue #3 - // not ended file writing will cause wrong responding. - // using temp files can mostly avoid the case. - - fs.writeFile( - filepath + hash + '.tmp', - JSON.stringify({content: minized}), - { encoding: 'utf8' }, - function (err) { - if (err) { - return callback(err); - } - fs.rename(filepath + hash + '.tmp', filepath + hash, callback); - } - ); -} - -function cacheGetMem(hash, callback) { - if (typeof callback !== 'function') { - return; - } - - if (!memCache.hasOwnProperty(hash)) { - callback(new Error('miss')); - } else { - callback(null, memCache[hash]); - } -} - -function cachePutMem(hash, minized, callback) { - memCache[hash] = minized; - - if (typeof callback === 'function') { - callback(null); - } -} - -module.exports = function express_minify(options) { - options = options || {}; - - var minifierInstances = { - uglifyJS: options.uglifyJS || uglifyjs, - cssmin: options.cssmin || cssmin - }; - - var js_match = options.js_match || /javascript/; - var css_match = options.css_match || /css/; - var sass_match = options.sass_match || /scss/; - var less_match = options.less_match || /less/; - var stylus_match = options.stylus_match || /stylus/; - var coffee_match = options.coffee_match || /coffeescript/; - var json_match = options.json_match || /json/; - var cache = options.cache || false; - - var cache_get = cacheGetMem; - var cache_put = cachePutMem; - - if (cache) { - cache = path.normalize(cache + '/').toString(); - - fs.writeFile(cache + 'test.tmp', new Date().getTime().toString(), function (err) { - if (err) { - console.log('WARNING: express-minify cache directory is not valid or is not writeable.'); - return; - } - - //Consider deleting the test file? - - //OK: rewrite functions - cache_get = function () { - return cacheGetFile.apply(cache, arguments); - }; - cache_put = function () { - return cachePutFile.apply(cache, arguments); - }; - }); - } - - return function middleware(req, res, next) { - var write = res.write; - var end = res.end; - - var buf = null; - var type = TYPE_TEXT; - - onHeaders(res, function () { - if (req.method === 'HEAD') { - return; - } - - if (res._skip) { - return; - } - - var contentType = res.getHeader('Content-Type'); - if (contentType === undefined) { - return; - } - - // for sass, less, stylus, coffee module: - // null: module is found but not loaded - // false: module not found - // so we should not process false values, but allow null values - if (sass !== false && sass_match.test(contentType)) { - type = TYPE_SASS; - res.setHeader('Content-Type', 'text/css'); - } else if (less !== false && less_match.test(contentType)) { - type = TYPE_LESS; - res.setHeader('Content-Type', 'text/css'); - } else if (stylus !== false && stylus_match.test(contentType)) { - type = TYPE_STYLUS; - res.setHeader('Content-Type', 'text/css'); - } else if (coffee !== false && coffee_match.test(contentType)) { - type = TYPE_COFFEE; - res.setHeader('Content-Type', 'text/javascript'); - } else if (json_match.test(contentType)) { - type = TYPE_JSON; - } else if (js_match.test(contentType)) { - type = TYPE_JS; - } else if (css_match.test(contentType)) { - type = TYPE_CSS; - } - - if (type === TYPE_TEXT) { - return; - } - - if ((type === TYPE_JS || type === TYPE_CSS) && res._no_minify) { - return; - } - - res.removeHeader('Content-Length'); - - // prepare the buffer - buf = []; - }); - - res.write = function (chunk, encoding) { - if (!this._header) { - this._implicitHeader(); - } - - if (buf === null) { - return write.call(this, chunk, encoding); - } - - if (!this._hasBody) { - return true; - } - - if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk)) { - throw new TypeError('first argument must be a string or Buffer'); - } - - if (chunk.length === 0) { - return true; - } - - // no chunked_encoding here - if (typeof chunk === 'string') { - chunk = new Buffer(chunk, encoding); - } - - buf.push(chunk); - }; - - res.end = function (data, encoding) { - if (this.finished) { - return false; - } - - if (!this._header) { - this._implicitHeader(); - } - - if (data && !this._hasBody) { - data = false; - } - - if (buf === null) { - return end.call(this, data, encoding); - } - - // TODO: implement hot-path optimization - if (data) { - this.write(data, encoding); - } - - var buffer = Buffer.concat(buf); - - // prepare uglify options - var uglifyOptions = {}; - if (this._no_mangle) { - uglifyOptions.mangle = false; - } - if (this._uglifyMangle !== undefined) { - uglifyOptions.mangle = this._uglifyMangle; - } - if (this._uglifyOutput !== undefined) { - uglifyOptions.output = this._uglifyOutput; - } - if (this._uglifyCompress !== undefined) { - uglifyOptions.compress = this._uglifyCompress; - } - - var minifyOptions = { - uglifyOpt: uglifyOptions, - noMinify: this._no_minify - }; - - var cacheKey = crypto.createHash('sha1').update(JSON.stringify(minifyOptions) + buffer).digest('hex').toString(); - var _this = this; - - cache_get(cacheKey, function (err, minized) { - if (err) { - // cache miss - minifyIt(type, minifyOptions, minifierInstances, buffer.toString(encoding), function (minized) { - if (_this._no_cache) { - // do not save cache for this response - write.call(_this, minized, 'utf8'); - end.call(_this); - } else { - cache_put(cacheKey, minized, function () { - write.call(_this, minized, 'utf8'); - end.call(_this); - }); - } - }); - } else { - // cache hit - write.call(_this, minized, 'utf8'); - end.call(_this); - } - }); - }; - - next(); - }; -}; diff --git a/package.json b/package.json index 786fb25..1feded8 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ { "name": "express-minify", "author": "Breezewish", - "description": "An express middleware to automatically minify and cache your javascript and css files.", + "description": "Automatically minify and cache your javascript and css files.", "homepage": "https://github.com/SummerWish/express-minify", "license": "MIT", - "version": "0.1.7", - "main": "minify.js", + "version": "0.2.0", + "main": "index.js", "keywords": [ "uglify", "minify", "express" ], "engines": { - "node": ">= 0.8.0" + "node": ">= 0.12.0" }, "repository": { "type": "git", diff --git a/test/test.js b/test/test.js index 9554a9f..c10cad1 100644 --- a/test/test.js +++ b/test/test.js @@ -37,7 +37,7 @@ var expectation = { {src: 'square = (x) -> x * x'} ], 'json': [ - {src: '{ "name" : "express-minify" , "author" : "Breezewish" , "description" : "An express middleware to automatically minify and cache your javascript and css files."}'} + {src: '{ "name" : "express-minify" , "author" : "Breezewish" , "description" : "test string"}'} ] }; @@ -117,6 +117,23 @@ describe('minify()', function() { }); + it('allow to customize error handling', function(done) { + var content = '/* this is a broken JavaScript!'; + var expected = 'success!'; + var server = createServer([minify({ + onerror: function (err, stage, assetType, minifyOptions, body, callback) { + callback(null, expected); + } + })], function(req, res) { + res.setHeader('Content-Type', 'text/javascript'); + res.end(content); + }); + request(server) + .get('/') + .expect(expected, done); + }); + + it('allow to customize UglifyJS instance', function(done) { var content = 'js_test'; var expected = 'js_test_passed';