chameleon-tool
Version:
chameleon 脚手架工具
422 lines (396 loc) • 12.4 kB
JavaScript
const webpack = require('webpack');
const devServer = require('./web/dev-server.js');
const getConfig = require('../configs/index.js');
const {startServer: startWeexLiveLoad, broadcast} = require('./weex/socket-server.js');
const previewSocket = require('./web/web-socket.js');
const cmlLinter = require('chameleon-linter');
const watch = require('glob-watcher');
const fse = require('fs-extra');
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const cmlUtils = require('chameleon-tool-utils');
const {getEntryName} = require('../configs/utils.js');
/**
* 非web端构建
* @param {*} media dev or build ...
* @param {*} type wx web weex
*/
exports.getBuildPromise = async function (media, type) {
let options = exports.getOptions(media, type);
let webpackConfig = await getConfig(options);
// 非web和weex 并且非增量
if (!~['web', 'weex'].indexOf(type) && options.increase !== true) {
// 异步删除output目录
var outputpath = webpackConfig.output.path;
if (outputpath) {
await new Promise(function(resolve, reject) {
fse.remove(outputpath, function(err) {
if (err) {
reject(err);
}
resolve();
})
})['catch'](e => {
let message = `clear file error! please remove direction ${outputpath} by yourself!`
cml.log.error(message);
throw new Error(e)
})
}
}
// 工程配置输出
let projectConfig = options && options.projectConfig;
let destDir = path.resolve(cml.projectRoot, `dist/${type}`);
// 支付宝小程序没有工程配置
if (['wx', 'qq', 'tt'].includes(type) && projectConfig) {
fse.outputJsonSync(path.join(destDir, 'project.config.json'), projectConfig, {spaces: 4})
}
if (type === 'baidu' && projectConfig) {
fse.outputJsonSync(path.join(destDir, 'project.swan.json'), projectConfig, {spaces: 4})
}
// sitemap配置输出
let siteMap = options && options.siteMap;
if (type === 'wx' && siteMap) {
fse.outputJsonSync(path.join(destDir, 'sitemap.json'), siteMap, {spaces: 4})
}
return new Promise(function(resolve, reject) {
// watch模式
if (media === 'dev') {
const compiler = webpack(webpackConfig);
if (type === 'weex') {
startWeexLiveLoad(options);
}
let optimizeCML = cml.config.get().optimize;
let watchOptions = {
aggregateTimeout: 300,
poll: undefined
}
if (optimizeCML && !optimizeCML.watchNodeModules) {
watchOptions.ignored = /node_modules/;
}
compiler.watch(watchOptions, (err, stats) => {
if (type === 'weex') {
if (!(stats && stats.compilation && stats.compilation.errors && stats.compilation.errors.length > 0)) {
broadcast('weex_refresh');
}
previewSocket.broadcast('weex_refresh');
}
if (err) {
reject(err);
}
resolve();
});
} else {
// build模式
webpack(webpackConfig, (err, stats) => {
if (err) {
reject(err);
}
resolve();
});
}
})
}
/**
* 获取webpack配置
* @param {*} media dev or build ...
* @param {*} type wx web weex
*/
exports.getOptions = function (media, type) {
let chameleonConfig = (cml.config.get() && cml.config.get()[type] && cml.config.get()[type][media]) || {};
if (!chameleonConfig) {
cml.log.error(`在chameleon的config中未找到 ${media}的配置参数`);
return;
}
let options = cml.utils.merge(
chameleonConfig,
{
type: type,
media,
root: cml.projectRoot,
buildType: 'weex' // 传递给dev-server,判断启动devserver的类型 暂时固定为weex
}
)
return options;
}
/**
* web端构建
* @param {*} type dev or build or 其他
* @param {*} isCompile 是否要编译web端 不编译web时也要启动web端服务
*/
exports.getWebBuildPromise = async function (media, isCompile) {
if (media === 'dev') {
let options = exports.getOptions(media, 'web');
let webpackConfig = await getConfig(options)
let compiler;
if (isCompile) {
compiler = webpack(webpackConfig);
}
return devServer({webpackConfig, options, compiler});
} else {
if (isCompile) {
return exports.getBuildPromise(media, 'web');
} else {
return Promise.resolve();
}
}
}
/**
* 构建所有端
* @param {*} media dev or build ...
* @param {*} type wx web weex
*/
exports.startReleaseAll = async function (media) {
if (media === 'build') {
process.env.NODE_ENV = 'production';
}
let cmlConfig = cml.config.get();
let allPlatform = cmlConfig.platforms;
let offPlatform = [];
let activePlatform = []; // 启动编译的platform
if (media === 'dev') {
offPlatform = cmlConfig.devOffPlatform;
} else if (media === 'build') {
offPlatform = cmlConfig.buildOffPlatform;
}
// 获取激活平台
for (let i = 0, j = allPlatform.length; i < j; i++) {
let platform = allPlatform[i];
if (!~offPlatform.indexOf(platform)) {
activePlatform.push(platform)
}
}
// 是否编译web端
let isCompile = !!~activePlatform.indexOf('web');
// 给preview使用
cml.activePlatform = activePlatform;
for (let i = 0, j = activePlatform.length; i < j; i++) {
let platform = activePlatform[i];
if (platform !== 'web') {
await exports.getBuildPromise(media, platform);
}
}
await exports.getWebBuildPromise(media, isCompile);
if (media === 'build') {
exports.createConfigJson()
}
startCmlLinter(media);
}
exports.startReleaseOne = async function(media, type) {
if (media === 'build') {
process.env.NODE_ENV = 'production';
}
// 给preview使用
cml.activePlatform = [type];
if (type === 'web') {
await exports.getWebBuildPromise(media, true);
} else {
let build = exports.getBuildPromise(media, type);
// 如果dev模式再启动web服务
if (media === 'dev') {
await build.then(res => {
exports.getWebBuildPromise(media, false);
})
} else {
await build;
}
}
if (media === 'build') {
exports.createConfigJson()
}
startCmlLinter(media);
}
// let lastLintTime = null;
function startCmlLinter(media) {
if (cml.config.get().enableLinter === true && media === 'dev') {
cmlLinter(cml.projectRoot);
const watcher = watch([cml.projectRoot + '/src/**/**.**']);
watcher.on('change', function(path, stat) {
cmlLinter(cml.projectRoot);
});
}
}
exports.getMD5 = function(weexjs) {
if (!weexjs) {
return '';
}
const weexjsName = weexjs.split('/').pop();
const weexjsPath = path.resolve(cml.projectRoot, 'dist/weex/', weexjsName);
if (cml.utils.isFile(weexjsPath)) {
const md5sum = crypto.createHash('md5');
const buffer = fs.readFileSync(weexjsPath);
md5sum.update(buffer);
let md5str = md5sum.digest('hex').toUpperCase();
return md5str
}
}
exports.createConfigJson = function() {
let configJsonPath = path.join(cml.projectRoot, 'dist/config.json');
let configObj = {};
if (cml.utils.isFile(configJsonPath)) {
configObj = JSON.parse(fs.readFileSync(configJsonPath, {encoding: 'utf-8'}))
}
// 获取weex jsbundle地址
let config = cml.config.get();
config.buildInfo = config.buildInfo || {};
let {wxAppId = '', baiduAppId = '', alipayAppId = '', qqAppId = '', ttAppId = ''} = config.buildInfo;
let extPlatform = config.extPlatform ;
let extCommand = (typeof extPlatform === 'object') ? Object.keys(extPlatform)[0] : undefined;
let extAppId = ''
if (extCommand) { // extCommand 可能值为 tt quickApp
extAppId = config.buildInfo && config.buildInfo[`${extCommand}AppId`]
}
let {routerConfig, hasError} = cml.utils.getRouterConfig();
let entryName = getEntryName();
let weexjs = (configObj[entryName] && configObj[entryName].js) || '';
let md5str = exports.getMD5(weexjs);
let mpa = routerConfig.mpa;
if (hasError) {
throw new Error('router.config.json格式不正确')
}
let result = [];
if (routerConfig) {
if (~cml.activePlatform.indexOf('web') && !routerConfig.domain) {
throw new Error('router.config.json 中未设置web端需要的domain字段');
}
let {domain, mode} = routerConfig;
routerConfig.routes.forEach(item => {
// 如果配置了weex多页面,那么每个路由都要重新计算对应的weexjs
if (mpa && mpa.weexMpa && Array.isArray(mpa.weexMpa)) { // 配置了weex多页面
let weexMpa = mpa.weexMpa;
for (let i = 0; i < weexMpa.length ; i++) {
if (Array.isArray(weexMpa[i].paths) && weexMpa[i].paths.includes(item.path)) {
if (typeof weexMpa[i].name === 'string') {
weexjs = configObj[`${weexMpa[i].name}`] && configObj[`${weexMpa[i].name}`].js;
} else {
weexjs = configObj[`${entryName}${i}`] && configObj[`${entryName}${i}`].js;
}
// weexjs = configObj[`${entryName}${i}`] && configObj[`${entryName}${i}`].js;
md5str = exports.getMD5(weexjs);
}
}
}
let webUrl = domain;
if (mode === 'history') {
webUrl += item.url;
} else if (mode === 'hash') {
webUrl += ('#' + item.url);
}
let route = {
wx: {
appId: wxAppId,
path: item.path
},
baidu: {
appId: baiduAppId,
path: item.path
},
alipay: {
appId: alipayAppId,
path: item.path
},
qq: {
appId: qqAppId,
path: item.path
},
tt: {
appId: ttAppId,
path: item.path
},
web: {
url: webUrl
},
weex: {
url: weexjs,
md5: md5str,
query: {
path: item.path
}
}
}
if (item.extra) {
route.extra = item.extra;
}
if (extCommand) {
route[extCommand] = {
appId: extAppId,
path: item.path
}
}
result.push(route);
})
// 处理subProject配置的npm包中cml项目的页面
let subProject = cml.config.get().subProject;
if (subProject && subProject.length > 0) {
subProject.forEach(function(item) {
let npmName = cmlUtils.isString(item) ? item : item.npmName;
let npmRouterConfig = cml.utils.readsubProjectRouterConfig(cml.projectRoot, npmName);
npmRouterConfig.routes && npmRouterConfig.routes.forEach(item => {
let cmlFilePath = path.join(cml.projectRoot, 'node_modules', npmName, 'src', item.path + '.cml');
let routePath = cml.utils.getPureEntryName(cmlFilePath, '', cml.projectRoot);
routePath = cml.utils.handleSpecialChar(routePath);
let webUrl = domain;
if (mode === 'history') {
webUrl += item.url;
} else if (mode === 'hash') {
webUrl += ('#' + item.url);
}
if (routePath[0] !== '/') {
routePath = '/' + routePath;
}
let route = {
wx: {
appId: wxAppId,
path: routePath
},
baidu: {
appId: baiduAppId,
path: routePath
},
alipay: {
appId: alipayAppId,
path: routePath
},
qq: {
appId: qqAppId,
path: routePath
},
tt: {
appId: ttAppId,
path: item.path
},
web: {
url: webUrl
},
weex: {
url: weexjs,
query: {
path: routePath
}
}
}
if (item.extra) {
route.extra = item.extra;
}
if (extCommand) {
route[extCommand] = {
appId: extAppId,
path: item.path
}
}
result.push(route);
})
})
}
}
result.forEach(item => {
Object.keys(item).forEach(key => {
if (!~cml.activePlatform.indexOf(key) && key !== 'extra') {
delete item[key]
}
})
})
cml.event.emit('config-json', result);
/* eslint-disable-next-line */
fse.outputFileSync(configJsonPath, JSON.stringify(result, '', 4))
}