UNPKG

builder-isv

Version:

ISV 模块本地预览与云构建器

182 lines (157 loc) 5.72 kB
/** * @author 龙喜<xiaolong.lxl@alibaba-inc.com> * @description rax */ 'use strict'; const co = require('co'); const path = require('path'); const utils = require('../../lib/utils'); const Base = require('../../models/base'); // https://nodejs.org/dist/latest-v6.x/docs/api/events.html const CompilationEventsPlugin = require('../../webpackPlugins/webpackPluginCompilationEvents'); const InstallDepsPlugin = require('../../webpackPlugins/webpackPluginInstallDeps'); const EventEmitter = require('events'); const getConfig = require('isv-light-webpack-config').Config; const webpack = require('webpack'); /** * 构建器类需实现以下方法(遵循 Promise A+): * start() 开始构建 * stop() 停止构建 * changeEntry() 切换构建入口 * destroy() 销毁实例 * * 需触发以下事件: * compileStart 构建开始 * compileSucceed 构建成功 * compileFailed 构建失败 * compileFinished 构建完成 * * 注意:请打开构建器 watch 模式以使用缓存 * * @type {RaxBuilder} */ module.exports = class RaxBuilder extends EventEmitter { constructor(invokeType) { super(); this.invokeType = invokeType || Base.INVOKE_TYPE_PREVIEW; this.setMaxListeners(100); } /** * start build * @param {Object} config * @param {String} config.module * @param {String} config.cwd * @param {String} config.outputDir */ start(config) { co(function*() { this.cwd = config.cwd; this.moduleName = config.module.moduleName; this.outputDir = config.outputDir; // 拼装 entry let entryObj = {}; if (this.invokeType === Base.INVOKE_TYPE_PREVIEW) { entryObj[this.moduleName + '/index'] = path.join(config.module.modulePath, 'index.js'); } else { entryObj[this.moduleName] = path.join(config.module.modulePath, 'index.js'); } utils.log.verbose('renderer invokeType', this.invokeType); utils.log.verbose('entryObj', entryObj); let abcInfo = utils.deepClone(config.module.abcInfo); let dependencies = config.module.packageInfo.dependencies; let componentsInUpx = yield utils.getVersionFromUpx(dependencies); utils.log.verbose('UPX 中的依赖:', componentsInUpx); // 主要用于 externals,预览里没啥用处 abcInfo.componentsInUpx = componentsInUpx; abcInfo.packageJSON = config.module.packageInfo; abcInfo.options.entry = entryObj; abcInfo.options.context = config.module.modulePath; // 兼容旧的逻辑,F**k // 代表是在 builder-mod 里面使用,有很多插件读这个属性,所以保留 abcInfo.modBuild = true; if (this.invokeType === Base.INVOKE_TYPE_PREVIEW) { // 代表是在开发预览模式,有很多插件读这个属性,所以保留 abcInfo.isDev = true; } let webpackConfig = getConfig({ outputDir: this.outputDir, abcOptions: abcInfo }); // 模块依赖安装错误标记 let depsInstallFailed = false; // 事件监听 webpackConfig.plugins.push(new CompilationEventsPlugin({ onCompilation: () => { if (!depsInstallFailed) { this.emit('compileStart'); } } })); // 安装依赖 webpackConfig.plugins.push(new InstallDepsPlugin({ cwd: this.cwd, outputDir: path.join(this.outputDir, this.moduleName), invokeType: this.invokeType, onBeforeInstall: () => { // 前置 hook,用于清空「依赖安装错误」标记 depsInstallFailed = false; }, onError: (err) => { depsInstallFailed = true; this.emit('compileFailed', err); this.emit('compileFinished'); } })); //webpackConfig.bail = true; let compiler = this.compiler = webpack(webpackConfig); let compileCallback = (err, stats) => { // 如果「依赖安装错误」,那么也认为是构建失败 if (!depsInstallFailed) { if (err || stats.hasErrors()) { if (!err && stats.compilation.errors.length) { err = new Error(stats.compilation.errors[0].message); err.stack = stats.compilation.errors[0].details; } this.emit('compileFailed', err); } else { this.emit('compileSucceed'); } this.emit('compileFinished'); } }; if (this.invokeType === Base.INVOKE_TYPE_PREVIEW) { this.watcher = compiler.watch({ // watch options: aggregateTimeout: 300, // wait so long for more changes poll: false // use polling instead of native watchers }, compileCallback); } else { compiler.run(compileCallback); } }.bind(this)); } stop() { // TODO this.emit('compileFinished'); } /** * 变更构建入口(只针对预览模式) * @param module */ changeEntry(module) { const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin'); const entry = path.join(module.modulePath, 'index.js'); const name = `${module.moduleName}/index`; this.compiler.apply(new SingleEntryPlugin(module.workSpace, entry, name)); this.compiler.apply(new SingleEntryPlugin(module.workSpace, entry, `${name}.web`)); this.compiler.apply(new SingleEntryPlugin(module.workSpace, entry, `${name}.native`)); if (!this.compiler.options.entry) { this.compiler.options.entry = {}; } this.compiler.options.entry[name] = entry; this.watcher.invalidate(); } destroy() { // TODO this.emit('compileFinished'); } };