UNPKG

mip-builder

Version:
336 lines (283 loc) 8.69 kB
/** * @file 构建功能 * @author errorrik(errorrik@gmail.com) */ var objectAssign = require('object-assign'); var minimatch = require('minimatch'); var mkdirp = require('mkdirp'); var MipProcessor = require('mip-processor'); var fs = require('fs'); var path = require('path'); var FileInfo = require('./file-info'); var util = require('./util'); /** * 构建功能类 * * @param {Object} options 构建参数 * @param {string} options.dir 构建目录路径 * @param {string} options.outputDir 输出目录路径 * @param {Array} options.files 选择构建文件 * @param {Array} options.processors 构建处理器集合 */ function Builder(options) { this.options = objectAssign({files: []}, options); this.files = []; } /** * 获取要处理的文件列表 * * @return {Array.<FileInfo>} */ Builder.prototype.getFiles = function () { return this.files; }; /** * 设置报告输出对象 * * @param {Object} reporter 报告输出对象 */ Builder.prototype.setReporter = function (reporter) { this.reporter = reporter; }; /** * 通知一个消息,用于提示 * * @param {Object} msg 消息对象 */ Builder.prototype.notify = function (msg) { if (this.reporter) { switch (msg.type) { case 'PROCESS_FILE': case 'PROCESS_ITEM': this.reporter.clearLine(); this.reporter.info(' - ' + msg.body); break; case 'PROCESS_PROCESSOR_START': this.reporter.clearLine(); this.reporter.info(' - ' + msg.body); break; case 'PROCESS_PROCESSOR_END': this.reporter.clearLine(); this.reporter.info(' - ' + msg.body); this.reporter.info('--------'); break; case 'PROCESS_START': this.reporter.info('* ' + msg.body); this.reporter.info('--------'); break; case 'PROCESS_END': this.reporter.clearLine(); this.reporter.success('* ' + msg.body + '\n'); break; case 'LOAD_START': this.reporter.info('* ' + msg.body); break; case 'LOAD_END': this.reporter.info('* ' + msg.body + '\n'); break; case 'OUTPUT_START': this.reporter.info('* ' + msg.body); this.reporter.info('--------'); break; case 'OUTPUT_FILE': this.reporter.clearLine(); this.reporter.info(' ' + msg.body); break; case 'OUTPUT_END': this.reporter.clearLine(); this.reporter.info('* ' + msg.body + '\n'); break; case 'BUILD_START': // this.reporter.info('-> ' + msg.body); break; case 'BUILD_END': this.reporter.success(msg.body); break; } } }; /** * 构建准备,主要是读入文件 * * @return {Promise} */ Builder.prototype.prepare = function () { return new Promise(function (resolve, reject) { if (this.isPrepared) { resolve(); return; } var startTime = new Date(); this.notify({ type: 'LOAD_START', body: 'Files loading ...' }); traverseDir(this.options.dir, this); var files = this.files; var selectedFlag = []; // select by selectors var fileSelectors = this.options.files; fileSelectors.unshift('**/*'); fileSelectors.forEach(function (selector) { var isExclude = selector.indexOf('!') === 0; if (isExclude) { selector = selector.slice(1); } files.forEach(function (file, index) { var isMatch = minimatch( file.relativePath, selector, {matchBase: true} ); if (isMatch) { selectedFlag[index] = !isExclude; } }); }); this.files = files .filter(function (file, index) { return !!selectedFlag[index]; }) .map(function (file) { return new FileInfo(file); }); this.notify({ type: 'LOAD_END', body: 'Files loaded! (' + (new Date() - startTime) + 'ms)' }); this.isPrepared = true; resolve(); }.bind(this)); }; /** * 构建处理,使用设置好的 processor 挨个进行处理 * * @return {Promise} */ Builder.prototype.process = function () { if (this.isProcessed) { return Promise.resolve(); } var returnPromise = this.prepare(); var startTime = new Date(); this.notify({ type: 'PROCESS_START', body: 'Files process start ...' }); this.options.processors.forEach(function (processor) { if (Object.getPrototypeOf(processor) === Object.prototype) { var CustomProcessor = MipProcessor.derive(processor); processor = new CustomProcessor(); } if (processor) { returnPromise = returnPromise.then(function () { return processor.process(this); }.bind(this)); } }, this); returnPromise = returnPromise.then(function () { this.isProcessed = true; this.notify({ type: 'PROCESS_END', body: 'Files process finished! (' + (new Date() - startTime) + 'ms)' }); }.bind(this)); return returnPromise; }; /** * 将构建结果输出到设置目录 * * @return {Promise} */ Builder.prototype.output = function () { var outputDir = this.options.outputDir; var startTime = new Date(); this.notify({ type: 'OUTPUT_START', body: 'Output start ...' }); this.files.forEach(function (file) { if (file.outputPath) { var outputPaths = file.outputPaths || []; var fileBuffer = file.getDataBuffer(); outputPaths.push(file.outputPath); outputPaths.forEach(function (outputPath) { var outputFile = path.resolve(outputDir, outputPath); mkdirp.sync(path.dirname(outputFile)); fs.writeFileSync(outputFile, fileBuffer); this.notify({ type: 'OUTPUT_FILE', body: '[Output] ' + outputPath }); }, this); } }, this); this.notify({ type: 'OUTPUT_END', body: 'Output finished! (' + (new Date() - startTime) + 'ms)' }); return Promise.resolve(); }; /** * 构建启动入口 * * @return {Promise} */ Builder.prototype.build = function () { var startTime = new Date(); this.notify({ type: 'BUILD_START', body: 'Building start' }); return this.process() .then(this.output.bind(this)) .then(function () { this.notify({ type: 'BUILD_END', body: 'Builded! (' + (new Date() - startTime) + 'ms)' }); }.bind(this)); }; /** * 通过路径获取文件信息对象 * * @param {string} filePath 文件路径,可以是绝对路径,也可以是相对构建根的路径 * @return {FileInfo} */ Builder.prototype.getFile = function (filePath) { var isAbsolute = /[a-z]:/i.test(filePath) || filePath.indexOf('/') === 0; if (!isAbsolute) { filePath = filePath.replace(/\\/g, '/'); } return this.files.find(function (file) { return file[isAbsolute ? 'fullPath' : 'relativePath'] === filePath; }); }; /** * 遍历目录,用于 prepare 阶段文件筛选 * * @inner * @param {string} dir 要遍历的目录 * @param {Builder} builder 构建器对象 */ function traverseDir(dir, builder) { var files = fs.readdirSync(dir); files.forEach(function (file) { var fullPath = path.resolve(dir, file); var relativePath = path.relative(builder.options.dir, fullPath).replace(/\\/g, '/'); try { var stat = fs.statSync(fullPath); if (stat.isDirectory()) { traverseDir(fullPath, builder); } else { builder.files.push({ fullPath: fullPath, relativePath: relativePath }); } } catch (e) {} }); } module.exports = exports = Builder;