UNPKG

fabricate

Version:

Combines mincer and uglify-js to build dependent files and a source map

134 lines (114 loc) 4.33 kB
var Mincer = require('mincer'), Uglify = require('uglify-js'), path = require('path'), fs = require('fs'), mkdirp = require('mkdirp'), _ = require('underscore'); function normalizeExtensions(pathname) { return pathname.replace(".css.less",".css") .replace(".css.scss",".css") .replace(".css.styl",".css") .replace(".less",".css") .replace(".scss",".css") .replace(".styl",".css") .replace(".js.coffee",".js") .replace(".coffee",".js"); } function buildForAsset(asset, srcDir, destDir, loadOrder) { var isTopLevel = false, requiredAssets = asset.requiredAssets || []; if (!_.isArray(loadOrder)) { loadOrder = []; isTopLevel = true; } requiredAssets.forEach(function(reqAsset){ var targetPathname; if (loadOrder.indexOf(reqAsset.pathname) !== -1) { return; } if (reqAsset.pathname === asset.pathname) { targetPathname = path.join(destDir, path.relative(process.cwd(), asset.pathname)); targetPathname = normalizeExtensions(targetPathname); mkdirp.sync(path.dirname(targetPathname)); fs.writeFileSync(targetPathname, asset.source); loadOrder.push(targetPathname); } else { buildForAsset(reqAsset, srcDir, destDir, loadOrder); } }); if (isTopLevel) { return loadOrder; } } var Fabricate = module.exports = function Fabricate(options) { this.validateOptions(options); // src needs to be just the file name of a file that can be found in // the include paths this.src = path.basename(options.src); this.dest = options.dest; // Later the dirname of src is added to include if it isnt already // present, so include could not be defined, but needs to be an array this.include = options.include || []; // sourceMap could be a string which is the name of the file that will // be written to disk, or could be false to not use source maps. If // option is not defined infer a name based on the source file this.sourceMap = options.sourceMap === undefined ? this.src + '.map' : options.sourceMap; this.tmpDir = options.tmpDir || 'tmp'; this.compress = options.compress || false; // Accept path to src as the options.src and add the options.src dirname // to the include list if it isn't already there. if (this.include.indexOf(path.dirname(options.src)) === -1) { this.include.push(path.dirname(options.src)); } }; Fabricate.prototype.validateOptions = function(options) { var err = null; if (typeof options !== 'object' || options instanceof Array) { err = 'Options object must be defined'; } if (!options.src) { err = 'src option is required'; } if (!options.dest) { err = 'dest option is required'; } if (err) { throw new Error(err); } }; Fabricate.prototype.build = function(callback) { this.minceAssets(callback); }; Fabricate.prototype.minceAssets = function(callback) { var asset, environment; environment = new Mincer.Environment(process.cwd()); this.include.forEach(function(include) { environment.appendPath(include); }); asset = environment.findAsset(this.src, { bundle: false }); if (!asset) { return callback('Cannot find logical path: ' + this.src); } this.uglifyAssets(_.uniq( buildForAsset(asset, path.dirname(asset.pathname), this.tmpDir) ), callback); }; Fabricate.prototype.uglifyAssets = function(loadOrder, callback) { var result, code, map, options = { outSourceMap: this.sourceMap, compress: false, mangle: false }; if (this.compress) { options = _.extend({}, options, { compress: {}, mangle: true }); } if (!this.sourceMap) { delete options.outSourceMap; } result = Uglify.minify(loadOrder, options); code = result.code; mkdirp.sync(path.dirname(this.dest)); if (this.sourceMap) { map = JSON.parse(result.map); map.sources = map.sources.map(function(source) { return source.replace(this.tmpDir, ''); }.bind(this)); fs.writeFileSync(path.join(path.dirname(this.dest), this.sourceMap), JSON.stringify(map)); // code += "\n//# sourceMappingURL="+this.sourceMap; code += "\n//@ sourceMappingURL="+this.sourceMap; } fs.writeFileSync(this.dest, code); callback(null); };