UNPKG

chameleon-tool

Version:

chameleon 脚手架工具

422 lines (396 loc) 12.4 kB
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)) }