UNPKG

buildr

Version:

The (Java|Coffee)Script and (CSS|Less) (Builder|Bundler|Packer|Minifier|Merger|Checker)

1,010 lines (949 loc) 31.5 kB
(function() { var Buildr, caterpillar, coffee, csslint, cwd, fs, jshint, jsp, less, path, pro, pulverizr, rimraf, uglify, util, watchTree, _base, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __slice = [].slice; fs = require('fs'); path = require('path'); util = require('bal-util'); coffee = require('coffee-script'); less = require('less'); pulverizr = require('pulverizr'); csslint = require('csslint').CSSLint; jshint = require('jshint').JSHINT; uglify = require('uglify-js'); jsp = uglify.parser; pro = uglify.uglify; cwd = process.cwd(); watchTree = false; caterpillar = require('caterpillar'); rimraf = require('rimraf'); (_base = Array.prototype).has || (_base.has = function(value2) { var value1, _i, _len; for (_i = 0, _len = this.length; _i < _len; _i++) { value1 = this[_i]; if (value1 === value2) { return true; } } return false; }); Buildr = (function() { Buildr.prototype.config = { name: null, log: true, watch: false, buildHandler: false, rebuildHandler: false, successHandler: false, srcPath: false, outPath: false, checkScripts: false, checkStyles: false, jshintOptions: false, csslintOptions: false, compressScripts: false, compressStyles: false, compressImages: false, scriptsOrder: false, stylesOrder: false, bundleScriptPath: false, bundleStylePath: false, deleteBundledFiles: true, srcLoaderHeader: false, srcLoaderPath: false }; Buildr.prototype.filesToClean = []; Buildr.prototype.errors = []; Buildr.prototype.watching = false; Buildr.prototype.processing = false; Buildr.prototype.logger = false; function Buildr(options) { var key, tmp, value, _base1, _base2, _ref, _this = this; if (options == null) { options = {}; } this.log = __bind(this.log, this); this.filesToClean = []; this.errors = []; tmp = {}; _ref = this.config; for (key in _ref) { value = _ref[key]; tmp[key] = value; } for (key in options) { value = options[key]; tmp[key] = value; } this.config = tmp; (_base1 = this.config).buildHandler || (_base1.buildHandler = function(err) { if (err) { _this.log('error', err); throw err; } _this.log('info', 'Building completed'); if (_this.config.successHandler) { return _this.config.successHandler.call(_this); } }); (_base2 = this.config).rebuildHandler || (_base2.rebuildHandler = function(err) { if (err) { _this.log('error', err); throw err; } _this.log('info', 'ReBuilding completed'); if (_this.config.successHandler) { return _this.config.successHandler.call(_this); } }); if (this.config.log === true) { this.config.log = 6; } this.logger || (this.logger = this.config.logger || new caterpillar.Logger({ level: this.config.log || 6, transports: { level: this.config.log || 6, formatter: { module: module } } })); true; } Buildr.prototype.log = function() { var args, type; args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; if (this.config.name) { type = args.shift(); args.unshift("[" + this.config.name + "]"); args.unshift(type); } return this.logger.log.apply(this.logger, args); }; Buildr.prototype.watch = function(next) { var buildr, log, watcher; if (this.watching) { return; } else { this.watching = true; } if (!watchTree) { watchTree = require('watch-tree-maintained'); } buildr = this; log = this.log; next || (next = this.config.rebuildHandler || this.config.buildHandler); log('debug', 'Setting up watching...'); watcher = watchTree.watchTree(this.config.srcPath); watcher.on('fileDeleted', function(path) { return buildr.process(function() { log('info', 'Rebuilt due to file delete at ' + (new Date()).toLocaleString()); return next.apply(next, arguments); }); }); watcher.on('fileCreated', function(path, stat) { return buildr.process(function() { log('info', 'Rebuilt due to file create at ' + (new Date()).toLocaleString()); return next.apply(next, arguments); }); }); watcher.on('fileModified', function(path, stat) { return buildr.process(function() { log('info', 'Rebuilt due to file change at ' + (new Date()).toLocaleString()); return next.apply(next, arguments); }); }); log('debug', 'Watching setup'); return next(false); }; Buildr.prototype.process = function(next) { var log, _this = this; log = this.log; next || (next = this.config.buildHandler); if (this.processing) { log('info', 'Processing postponed'); return; } this.processing = true; log('info', 'Processing started'); if (this.config.watch) { this.watch(); } return this.checkConfiguration(function(err) { if (err) { return next(err); } return _this.checkFiles(function(err) { if (err) { return next(err); } return _this.cpSrcToOut(function(err) { if (err) { return next(err); } return _this.generateFiles(function(err) { if (err) { return next(err); } return _this.cleanOutPath(function(err) { if (err) { return next(err); } return _this.compressFiles(function(err) { _this.processing = false; log('info', 'Processing finished'); return next(err); }); }); }); }); }); }); }; Buildr.prototype.checkConfiguration = function(next) { var log, tasks, _base1, _this = this; if (!this.config.srcPath) { return next(new Error('srcPath is required')); } log = this.log; tasks = new util.Group(function(err) { log('debug', 'Checked configuration'); return next(err); }); tasks.total = 6; log('debug', 'Checking configuration'); (_base1 = this.config).outPath || (_base1.outPath = this.config.srcPath); this.config.srcPath = path.resolve(this.config.srcPath); tasks.complete(null); this.config.outPath = path.resolve(this.config.outPath); tasks.complete(null); if (this.config.bundleScriptPath) { this.config.bundleScriptPath = path.resolve(this.config.bundleScriptPath); tasks.complete(null); } else { tasks.complete(false); } if (this.config.bundleStylePath) { this.config.bundleStylePaths = path.resolve(this.config.bundleStylePath); tasks.complete(null); } else { tasks.complete(false); } if (this.config.srcLoaderPath) { this.config.srcLoaderPath = path.resolve(this.config.srcLoaderPath); tasks.complete(null); } else { tasks.complete(false); } if (this.config.srcPath === this.config.outPath) { this.config.deleteBundledFiles = false; if (this.config.compressScripts) { this.config.compressScripts = this.config.bundleScriptPath ? [this.config.bundleScriptPath] : false; } if (this.config.compressStyles) { this.config.compressStyles = this.config.bundleStylePath ? [this.config.bundleStylePath] : false; } if (this.config.compressImages) { this.config.compressImages = false; } } if (this.config.bundleScripts === true) { this.config.bundleScripts = false; } if (this.config.bundleStyles === true) { this.config.bundleStyles = false; } return tasks.complete(false); }; Buildr.prototype.checkFiles = function(next, config) { var log, _this = this; log = this.log; config || (config = this.config); if (!(config.checkScripts || config.checkStyles)) { return next(false); } log('debug', 'Check files'); this.forFilesInDirectory(config.srcPath, function(fileFullPath, fileRelativePath, next) { return _this.checkFile(fileFullPath, next); }, function(err) { if (err) { return next(err); } log('debug', 'Checked files'); return next(err); }); return true; }; Buildr.prototype.cpSrcToOut = function(next, config) { var log; log = this.log; config || (config = this.config); if (config.outPath === config.srcPath) { return next(false); } log('debug', "Copying " + config.srcPath + " to " + config.outPath); rimraf(config.outPath, function(err) { if (err) { return next(err); } return util.cpdir(config.srcPath, config.outPath, function(err) { log('debug', "Copied " + config.srcPath + " to " + config.outPath); return next(err); }); }); return true; }; Buildr.prototype.generateFiles = function(next) { var log, tasks; log = this.log; tasks = new util.Group(function(err) { log('debug', 'Generated files'); return next(err); }); tasks.total += 3; log('debug', 'Generating files'); this.generateSrcLoaderFile(tasks.completer()); this.generateBundledScriptFile(tasks.completer()); this.generateBundledStyleFile(tasks.completer()); return true; }; Buildr.prototype.generateSrcLoaderFile = function(next, config) { var loadedInTemplates, log, srcLoaderData, srcLoaderPath, templateTasks, templates, _this = this; config || (config = this.config); if (!config.srcLoaderPath) { return next(false); } log = this.log; log('debug', "Generating " + config.srcLoaderPath); templates = {}; srcLoaderData = ''; srcLoaderPath = config.srcLoaderPath; loadedInTemplates = null; templateTasks = new util.Group(function(err) { var script, style, _i, _j, _len, _len1, _ref, _ref1; if (err) { next(err); } srcLoaderData += "scripts = [\n"; _ref = config.scriptsOrder; for (_i = 0, _len = _ref.length; _i < _len; _i++) { script = _ref[_i]; srcLoaderData += "\t'" + script + "'\n"; } srcLoaderData += "\]\n\n"; srcLoaderData += "styles = [\n"; _ref1 = config.stylesOrder; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { style = _ref1[_j]; srcLoaderData += "\t'" + style + "'\n"; } srcLoaderData += "\]\n\n"; srcLoaderData += templates.srcLoader + "\n\n" + templates.srcLoaderHeader; return fs.writeFile(srcLoaderPath, srcLoaderData, function(err) { if (err) { return next(err); } srcLoaderData = coffee.compile(srcLoaderData); return fs.writeFile(srcLoaderPath, srcLoaderData, function(err) { if (err) { return next(err); } log('debug', "Generated " + config.srcLoaderPath); return next(false); }); }); }); templateTasks.total = config.srcLoaderHeader ? 1 : 2; fs.readFile(__dirname + '/templates/srcLoader.coffee', function(err, data) { if (err) { return templateTasks.exit(err); } templates.srcLoader = data.toString(); return templateTasks.complete(err); }); if (config.srcLoaderHeader) { templates.srcLoaderHeader = config.srcLoaderHeader; } else { fs.readFile(__dirname + '/templates/srcLoaderHeader.coffee', function(err, data) { if (err) { return templateTasks.exit(err); } templates.srcLoaderHeader = data.toString(); return templateTasks.complete(err); }); } return true; }; Buildr.prototype.generateBundledStyleFile = function(next, config) { var log, source, _this = this; log = this.log; config || (config = this.config); if (!config.bundleStylePath) { return next(false); } log('debug', "Generating " + config.bundleStylePath); source = ''; this.useOrScan(config.stylesOrder, this.config.outPath, function(fileFullPath, fileRelativePath, next) { var bundledRelativePath, extension, _fileFullPath, _fileRelativePath; extension = path.extname(fileRelativePath); switch (extension) { case '.css': _fileRelativePath = fileRelativePath; _fileFullPath = fileFullPath; fileRelativePath = _fileRelativePath.substring(0, _fileRelativePath.length - extension.length) + '.less'; fileFullPath = _fileFullPath.substring(0, _fileFullPath.length - extension.length) + '.less'; if (config.deleteBundledFiles) { _this.filesToClean.push(_fileFullPath); } _this.filesToClean.push(fileFullPath); bundledRelativePath = path.relative(path.dirname(path.resolve(config.bundleStylePath)), fileFullPath); return fs.exists(fileFullPath, function(exists) { if (exists) { source += "@import \"" + bundledRelativePath + "\";\n"; return next(false); } else { return fs.readFile(_fileFullPath, function(err, data) { data = data.toString().replace(/filter: ([^~"])(.*)$/gm, "filter: ~\"$1$2\""); return fs.writeFile(fileFullPath, data, function(err) { if (err) { return next(err); } source += "@import \"" + bundledRelativePath + "\";\n"; return next(false); }); }); } }); case '.less': if (config.deleteBundledFiles) { _this.filesToClean.push(fileFullPath); } bundledRelativePath = path.relative(path.dirname(path.resolve(config.bundleStylePath)), fileFullPath); source += "@import \"" + bundledRelativePath + "\";\n"; return next(false); default: return next(false); } }, function(err) { if (err) { return next(err); } log('debug', "Compiling " + config.bundleStylePath); return _this.compileStyleData(config.bundleStylePath, source, function(err, result) { if (err) { return next(err); } return fs.writeFile(config.bundleStylePath, result, function(err) { log('debug', "Generated " + config.bundleStylePath); return next(err, result); }); }); }); return true; }; Buildr.prototype.generateBundledScriptFile = function(next, config) { var log, results, _this = this; log = this.log; config || (config = this.config); if (!config.bundleScriptPath) { return next(false); } log('debug', "Generating " + config.bundleScriptPath); results = {}; this.useOrScan(config.scriptsOrder, config.outPath, function(fileFullPath, fileRelativePath, next) { var extension; extension = path.extname(fileRelativePath); switch (extension) { case '.js': case '.coffee': return _this.compileScriptFile(fileFullPath, function(err, result) { if (err) { return next(err); } results[fileRelativePath] = result; if (config.deleteBundledFiles) { _this.filesToClean.push(fileFullPath); } return next(err); }, false); default: return next(false); } }, function(err) { var fileRelativePath, result, _i, _len, _ref; if (err) { return next(err); } result = ''; if (config.scriptsOrder.has != null) { _ref = config.scriptsOrder; for (_i = 0, _len = _ref.length; _i < _len; _i++) { fileRelativePath = _ref[_i]; if (results[fileRelativePath] == null) { return next(new Error("The file " + fileRelativePath + " failed to compile")); } result += results[fileRelativePath]; } } return fs.writeFile(config.bundleScriptPath, result, function(err) { log('debug', "Generated " + config.bundleScriptPath); return next(err); }); }); return true; }; Buildr.prototype.cleanOutPath = function(next) { var fileFullPath, log, tasks; if (!(this.filesToClean || []).length) { return next(false); } log = this.log; tasks = new util.Group(function(err) { log('debug', 'Cleaned outPath'); return next(err); }); tasks.total += this.filesToClean.length; log('debug', 'Cleaning outPath'); while (this.filesToClean.length > 0) { fileFullPath = this.filesToClean.shift(); log('debug', "Cleaning " + fileFullPath); fs.unlink(fileFullPath, tasks.completer()); } return true; }; Buildr.prototype.compressFiles = function(next, config) { var log, tasks, _this = this; config || (config = this.config); if (!(config.compressScripts || config.compressStyles || config.compressImages)) { return next(false); } log = this.log; tasks = new util.Group(function(err) { log('debug', 'Compressed files'); return next(err); }); tasks.total += 1; log('debug', 'Compressing files'); if (config.compressScripts === true) { if (config.bundleScriptPath) { ++tasks.total; this.compressFile(config.bundleScriptPath, tasks.completer()); } if (config.bundleStylePath) { ++tasks.total; this.compressFile(config.bundleStylePath, tasks.completer()); } } this.useOrScan(config.compressScripts, config.outPath, function(fileFullPath, fileRelativePath, next) { return _this.compressFile(fileFullPath, next); }, tasks.completer()); return true; }; Buildr.prototype.forFilesInArray = function(files, parentPath, callback, next) { var fileRelativePath, log, tasks, _i, _len, _this = this; if (!(files || []).length) { return next(false); } log = this.log; tasks = new util.Group(function(err) { return next(err); }); tasks.total += files.length; for (_i = 0, _len = files.length; _i < _len; _i++) { fileRelativePath = files[_i]; callback(path.resolve(parentPath, fileRelativePath), fileRelativePath, tasks.completer()); } return true; }; Buildr.prototype.forFilesInDirectory = function(parentPath, callback, next) { util.scandir(parentPath, callback, false, next); return true; }; Buildr.prototype.useOrScan = function(files, parentPath, callback, next) { if (files === true) { this.forFilesInDirectory(parentPath, callback, next); } else if (files && files.length) { this.forFilesInArray(files, parentPath, callback, next); } else { next(false); } return true; }; Buildr.prototype.compileFile = function(fileFullPath, next) { var extension; extension = path.extname(fileFullPath); switch (extension) { case '.coffee': this.compileScriptFile(fileFullPath, next); break; case '.less': this.compileStyleFile(fileFullPath, next); break; default: next(false); } return true; }; Buildr.prototype.compressFile = function(fileFullPath, next, config) { var extension; config || (config = this.config); extension = path.extname(fileFullPath); switch (extension) { case '.js': if (config.compressScripts === true || (config.compressScripts.has != null) && config.compressScripts.has(fileFullPath)) { this.compressScriptFile(fileFullPath, next); } else { next(false); } break; case '.css': if (config.compressStyles === true || (config.compressStyles.has != null) && config.compressStyles.has(fileFullPath)) { this.compressStyleFile(fileFullPath, next); } else { next(false); } break; case '.gif': case '.jpg': case '.jpeg': case '.png': case '.tiff': case '.bmp': if (config.compressImages === true || (config.compressImages.has != null) && config.compressImages.has(fileFullPath)) { this.compressImageFile(fileFullPath, next); } else { next(false); } break; default: next(false); } return true; }; Buildr.prototype.checkFile = function(fileFullPath, next, config) { var extension; config || (config = this.config); extension = path.extname(fileFullPath); switch (extension) { case '.js': if (config.checkScripts === true || (config.checkScripts.has != null) && config.checkScripts.has(fileFullPath)) { this.checkScriptFile(fileFullPath, next); } else { next(false); } break; case '.css': if (config.checkStyles === true || (config.checkStyles.has != null) && config.checkStyles.has(fileFullPath)) { this.checkStyleFile(fileFullPath, next); } else { next(false); } break; default: next(false); } return true; }; Buildr.prototype.compressImageFile = function(fileFullPath, next) { var log; log = this.log; log('debug', "Compressing " + fileFullPath); try { pulverizr.compress(fileFullPath, { quiet: true }); log('debug', "Compressed " + fileFullPath); next(false); } catch (err) { next(err); } return true; }; Buildr.prototype.compileStyleData = function(fileFullPath, src, next) { var log, options, result; log = this.log; result = ''; options = { paths: [path.dirname(fileFullPath)], optimization: 1, filename: fileFullPath }; new less.Parser(options).parse(src, function(err, tree) { if (err) { log('debug', err); return next(new Error('Less compilation failed'), result); } else { try { result = tree.toCSS({ compress: 0 }); return next(false, result); } catch (err) { return next(err, result); } } }); return true; }; Buildr.prototype.compileStyleFile = function(fileFullPath, next, write) { var log, _this = this; if (write == null) { write = true; } log = this.log; fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err); } return _this.compileStyleData(fileFullPath, data.toString(), function(err, result) { if (err || !write) { return next(err, result); } return fs.writeFile(fileFullPath, result, function(err) { if (err) { return next(err); } return next(err, result); }); }); }); return true; }; Buildr.prototype.compressStyleData = function(fileFullPath, src, next) { var log, options, result; log = this.log; result = ''; options = { paths: [path.dirname(fileFullPath)], optimization: 1, filename: fileFullPath }; new less.Parser(options).parse(src, function(err, tree) { if (err) { log('debug', err); return next(new Error('Less compilation failed'), result); } else { try { result = tree.toCSS({ compress: 1 }); return next(false, result); } catch (err) { return next(err, result); } } }); return true; }; Buildr.prototype.compressStyleFile = function(fileFullPath, next, write) { var log, _this = this; if (write == null) { write = true; } log = this.log; log('debug', "Compressing " + fileFullPath); fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err); } return _this.compressStyleData(fileFullPath, data.toString(), function(err, result) { if (err || !write) { return next(err, result); } return fs.writeFile(fileFullPath, result, function(err) { if (err) { return next(err); } log('debug', "Compressed " + fileFullPath); return next(err, result); }); }); }); return true; }; Buildr.prototype.checkStyleData = function(fileFullPath, src, next, config) { var errord, formatId, log, message, result, _i, _len, _ref; log = this.log; config || (config = this.config); errord = false; result = csslint.verify(src, config.csslintOptions || {}); formatId = 'text'; if (!result.messages.length) { return next(false, false); } _ref = result.messages; for (_i = 0, _len = _ref.length; _i < _len; _i++) { message = _ref[_i]; if (!(message && message.type === 'error')) { continue; } errord = true; } if (errord) { log('error', csslint.getFormatter(formatId).formatResults(result, fileFullPath, formatId)); } return next(false, errord); }; Buildr.prototype.checkStyleFile = function(fileFullPath, next) { var log, _this = this; log = this.log; log('debug', "Checking " + fileFullPath); fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err, false); } return _this.checkStyleData(fileFullPath, data.toString(), function(err, errord) { if (err) { return next(err); } log('debug', "Checked " + fileFullPath); return next(err, errord); }); }); return true; }; Buildr.prototype.compileScriptData = function(extension, src, next) { var result; result = false; try { switch (extension) { case '.coffee': result = coffee.compile(src); break; case '.js': result = src; break; default: throw new Error('Unknown script type: ' + extension); } } catch (err) { next(err); } return next(false, result); }; Buildr.prototype.compileScriptFile = function(fileFullPath, next, write) { var log, _this = this; if (write == null) { write = true; } log = this.log; fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err); } return _this.compileScriptData(path.extname(fileFullPath), data.toString(), function(err, result) { if (err || !write) { return next(err, result); } return fs.writeFile(fileFullPath, result, function(err) { if (err) { return next(err); } return next(err, result); }); }); }); return true; }; Buildr.prototype.compressScriptData = function(src, next) { var ast, out; ast = jsp.parse(src); ast = pro.ast_mangle(ast); ast = pro.ast_squeeze(ast); out = pro.gen_code(ast); return next(false, out); }; Buildr.prototype.compressScriptFile = function(fileFullPath, next, write) { var log, _this = this; if (write == null) { write = true; } log = this.log; log('debug', "Compressing " + fileFullPath); fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err); } return _this.compressScriptData(data.toString(), function(err, result) { if (err || !write) { return next(err, result); } return fs.writeFile(fileFullPath, result, function(err) { if (err) { return next(err); } log('debug', "Compressed " + fileFullPath); return next(err, result); }); }); }); return true; }; Buildr.prototype.checkScriptData = function(fileFullPath, src, next, config) { var error, errord, evidence, log, message, result, _i, _len, _ref; log = this.log; config || (config = this.config); errord = false; jshint(src, config.jshintOptions || {}); result = jshint.data(); result.errors || (result.errors = []); if (!result.errors.length) { return next(false, false); } log('error', "\n" + fileFullPath + ":"); _ref = result.errors; for (_i = 0, _len = _ref.length; _i < _len; _i++) { error = _ref[_i]; if (!(error && error.raw)) { continue; } errord = true; message = error.raw.replace(/\.$/, '').replace(/\{([a-z])\}/, function(a, b) { return error[b] || a; }); evidence = error.evidence ? "\n\t" + error.evidence.replace(/^\s+/, '') : ''; log('warn', "\tLine " + error.line + ": " + message + " " + evidence + "\n"); } return next(false, errord); }; Buildr.prototype.checkScriptFile = function(fileFullPath, next) { var log, _this = this; log = this.log; log('debug', "Checking " + fileFullPath); fs.readFile(fileFullPath, function(err, data) { if (err) { return next(err, false); } return _this.checkScriptData(fileFullPath, data.toString(), function(err, errord) { if (err) { return next(err); } log('debug', "Checked " + fileFullPath); return next(err, errord); }); }); return true; }; return Buildr; })(); module.exports = { createInstance: function(options) { return new Buildr(options); } }; }).call(this);