builder-isv
Version:
ISV 模块本地预览与云构建器
182 lines (157 loc) • 5.72 kB
JavaScript
/**
* @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');
}
};