UNPKG

sp-boilerplate

Version:
490 lines (372 loc) 15.6 kB
// const fs = require('fs-extra') const path = require('path') // const webpack = require('webpack') const WebpackDevServer = require('webpack-dev-server') const WebpackConfig = require('webpack-config').default const common = require('./common') // 调试webpack模式 const DEBUG = 1 // 程序启动路径,作为查找文件的基础 const RUN_PATH = process.cwd() // 客户端开发环境webpack-dev-server端口号 const CLIENT_DEV_PORT = process.env.WEBPACK_DEV_SERVER_PORT || 3001 // 描述环境 // dev 开发 | dist 部署 const ENV = process.env.WEBPACK_BUILD_ENV || 'dev' // 描述场景 // client 客户端 | server 服务端 const STAGE = process.env.WEBPACK_STAGE_MODE || 'client' // 用户自定义系统配置 const SYSTEM_CONFIG = require('../../config/system') /** * 修复配置 * 配置有可能是 Array * * @param {any} config webpack的配置对象 * @returns 修复后的配置对象 */ function makeItButter(config) { // 数组情况,拆分每项分别处理 if (Array.isArray(config)) return config.map(thisConfig => makeItButter(thisConfig)) // no ref obj config = Object.assign({}, config) // try to fix a pm2 bug that will currupt [name] value if (config.output) { for (let key in config.output) { if (typeof config.output[key] === 'string') config.output[key] = config.output[key].replace(/-_-_-_-_-_-(.+?)-_-_-_-_-_-/g, '[name]') } } // remove all undefined from plugins if (!Array.isArray(config.plugins)) { config.plugins = [] } config.plugins = config.plugins.filter(plugin => typeof plugin !== 'undefined') // remove duplicate plugins // if (Array.isArray(config.plugins)) { // config.plugins = removeDuplicateObject(config.plugins) // } // remove duplicate rules if (Array.isArray(config.module.rules)) { config.module.rules = removeDuplicateObject(config.module.rules) } // 删除重复对象 function removeDuplicateObject(list) { let map = {} list = (() => { return list.map((rule) => { let key = JSON.stringify(rule) key = key.toLowerCase().replace(/ /g, '') if (map[key]) rule = undefined else map[key] = 1 return rule }) })() return list.filter(rule => rule != undefined) } if (process.env.WEBPACK_ANALYZE || config.analyzer) config.plugins.push( new (require('webpack-bundle-analyzer').BundleAnalyzerPlugin)() ) // custom logic use delete config.__ext delete config.spa delete config.analyzer delete config.htmlPath // no ref obj return config } /** * 分析并处理rules * eg: * rules: [{自定义rule}, 'default', {自定义rule}] * * @param {any} customRules * @param {any} defaultRules * @returns 处理后的rules */ function handlerRules(customRules) { const ruleMap = {} // 默认rules ruleMap['default'] = common.rules // 解析需要的rules // => if (customRules == 'default') { customRules = ruleMap['default'] } else // => if (Array.isArray(customRules)) { let _rlist = [] customRules.forEach((item) => { if (item == 'default') { _rlist = _rlist.concat(ruleMap['default']) } else { _rlist.push(item) } }) customRules = _rlist } return customRules } /** * 根据应用配置生产出一个默认webpack配置 * * @param {any} opt 应用配置 * @param {any} _path 读取默认配置文件地址,非必须 * @returns */ async function createDefaultConfig(opt, _path) { // 根据当前环境变量,定位对应的默认配置文件 _path = _path || path.resolve(RUN_PATH, `./system/webpack/${STAGE}/${ENV}.js`) const factory = await getConfigFactory(_path) const config = await factory(opt) return config } /** * 根据应用配置生产出一个默认webpack配置[客户端情况的SPA模式使用] * * @param {any} opt * @returns */ async function createSPADefaultConfig(opt) { return createDefaultConfig(opt, path.resolve(RUN_PATH, `./system/webpack/${STAGE}/${ENV}.spa.js`)) } /** * 获取配置生成的工厂方法 * * @param {any} path 工厂方法对应的文件路径 * @returns 工厂方法 */ async function getConfigFactory(path) { let factory if (fs.existsSync(path)) factory = await require(path) else console.log(`!!! ERROR !!! 没找到对应的配置文件: ${path}`) return factory } /** * Webpack 运行入口方法 */ async function justDoooooooooooooIt() { // webpack 执行用的配置对象 let webpackConfigs = [] DEBUG && console.log('============== Webpack Debug =============') DEBUG && console.log('Webpack 打包环境:', STAGE, ENV) /** * 处理客户端配置文件 * [n个应用] x [m个打包配置] = [webpack打包配置集合] */ async function handlerClientConfig() { // 把装载的所有子应用的 webpack 配置都加上 const appsConfig = await require('../../config/apps') for (let appName in appsConfig) { let opt = { RUN_PATH, CLIENT_DEV_PORT, APP_KEY: appName } let defaultConfig = await createDefaultConfig(opt) let defaultSPAConfig = await createSPADefaultConfig(opt) let appConfig = appsConfig[appName] // 如果没有webpack配置,则表示没有react,不需要打包 if (!appConfig.webpack) continue let clientConfigs = appConfig.webpack.client // 统一转成数组,支持多个client配置 if (!Array.isArray(clientConfigs)) { clientConfigs = [clientConfigs] } clientConfigs.forEach((clientConfig) => { let config = new WebpackConfig() clientConfig = new WebpackConfig().merge(clientConfig) // 跟进打包环境和用户自定义配置,扩展webpack配置 if (clientConfig.__ext) { clientConfig.merge(clientConfig.__ext[ENV]) } let _defaultConfig = (() => { let config = Object.assign({}, defaultConfig) // 如果是SPA应用 if (clientConfig.spa) { config = Object.assign({}, defaultSPAConfig) } return config })() // 如果自定义了,则清除默认 if (clientConfig.entry) _defaultConfig.entry = undefined if (clientConfig.output) _defaultConfig.output = undefined // // 如果自定义了plugins,则分析并实例化plugins内容 // if (clientConfig.plugins) { const pluginMap = {} // 默认plugins pluginMap['default'] = _defaultConfig.plugins _defaultConfig.plugins = undefined // 补充必须的打包环境变量 pluginMap['global'] = common.plugins(ENV, STAGE, clientConfig.spa) // sp扩展的plugins pluginMap['pwa'] = common.factoryPWAPlugin({ appName: appName, outputPath: '' }) // 字符串且等于default,使用默认plugins // => if (clientConfig.plugins == 'default') { clientConfig.plugins = pluginMap['global'].concat(pluginMap['default']) } else // 需要解析的plugins // => if (Array.isArray(clientConfig.plugins)) { let _plist = [] _plist = _plist.concat(pluginMap['global']) clientConfig.plugins.forEach((item) => { // 默认plugin列表 if (item == 'default') { _plist = _plist.concat(pluginMap['default']) } // 自定义plugin列表 if (Array.isArray(item)) { _plist = _plist.concat(item) } // sp的自定义plugin列表,key是名字,val是配置项 if (typeof item == 'object') { // sp的PWA配置 if (item['pwa']) { let autoConfig = { appName: appName, outputPath: path.resolve(clientConfig.output ? clientConfig.output.path : _defaultConfig.output.path, '../') } let opt = Object.assign({}, autoConfig, item['pwa']) _plist.push(common.factoryPWAPlugin(opt)) } // // .... 这里可以继续写sp自己的扩展plugin // } }) // 把解析好的plugin列表反赋值给客户端配置 clientConfig.plugins = _plist } // => else { new Error('plugins 配置内容有错误,必须是 array | [default]') } } else { // 未设置情况,需要补充给默认配置全局变量 _defaultConfig.plugins = _defaultConfig.plugins.concat(common.plugins(ENV, STAGE, clientConfig.spa)) } // // 如果自定义了loader,则分析并实例化loader // if (clientConfig.module && clientConfig.module.rules) { clientConfig.module.rules = handlerRules(clientConfig.module.rules) _defaultConfig.module.rules = undefined } config .merge(_defaultConfig) .merge(clientConfig) webpackConfigs.push(config) }) } } /** * 处理服务端配置文件 * [n个应用] 公用1个服务端打包配置,并且merge了client的相关配置 * 注:如果客户端的配置有特殊要求或者冲突,则需要手动调整下面的代码 */ async function handlerServerConfig() { // 服务端需要全部子项目的配置集合 // 先合并全部子项目的配置内容 // 再合并到服务端配置里 const appsConfig = await require('../../config/apps') let tempClientConfig = new WebpackConfig() for (let appName in appsConfig) { // 如果没有webpack配置,则表示没有react,不需要打包 if (!appsConfig[appName].webpack) continue let clientConfig = appsConfig[appName].webpack.client if (!Array.isArray(clientConfig)) clientConfig = [clientConfig] clientConfig.forEach((config) => { // // 如果自定义了loader,则分析并实例化loader // if (config.module && config.module.rules) { config.module.rules = handlerRules(config.module.rules) } tempClientConfig.merge(config) }) } let opt = { RUN_PATH, CLIENT_DEV_PORT } let defaultConfig = await createDefaultConfig(opt) let config = new WebpackConfig() // 注:在某些项目里,可能会出现下面的加载顺序有特定的区别,需要自行加判断 // 利用每个app的配置,设置 include\exclude 等。 config .merge(defaultConfig) .merge({ module: tempClientConfig.module || { rules: common.rules }, resolve: tempClientConfig.resolve, plugins: common.plugins(ENV, STAGE) }) // 如果用户自己配置了服务端打包路径,则覆盖默认的 if (SYSTEM_CONFIG.WEBPACK_SERVER_OUTPATH) config.output.path = path.resolve(RUN_PATH, SYSTEM_CONFIG.WEBPACK_SERVER_OUTPATH) webpackConfigs.push(config) } // 客户端开发模式 if (STAGE === 'client' && ENV === 'dev') { await handlerClientConfig() const compiler = webpack(makeItButter(webpackConfigs)) // more config // http://webpack.github.io/docs/webpack-dev-server.html const server = new WebpackDevServer(compiler, { quiet: false, stats: { colors: true }, hot: true, inline: true, contentBase: './', publicPath: '/dist/', headers: { 'Access-Control-Allow-Origin': '*' } }) server.listen(CLIENT_DEV_PORT) } // 客户端打包 if (STAGE === 'client' && ENV === 'dist') { // process.env.NODE_ENV = 'production' await handlerClientConfig() // 执行打包 const compiler = webpack(makeItButter(webpackConfigs)) compiler.run((err, stats) => { if (err) console.log(`webpack dist error: ${err}`) console.log(stats.toString({ chunks: false, // 输出精简内容 colors: true })) }) } // 服务端开发环境 if (STAGE === 'server' && ENV === 'dev') { await handlerServerConfig() webpack(makeItButter(webpackConfigs), (err, stats) => { if (err) console.log(`webpack dev error: ${err}`) console.log(stats.toString({ chunks: false, colors: true })) }) } // 服务端打包 if (STAGE === 'server' && ENV === 'dist') { // process.env.NODE_ENV = 'production' await handlerServerConfig() webpack(makeItButter(webpackConfigs), (err, stats) => { if (err) console.log(`webpack dist error: ${err}`) console.log(stats.toString({ chunks: false, // Makes the build much quieter colors: true })) }) } DEBUG && console.log('执行配置:') DEBUG && console.log('-----------------------------------------') DEBUG && console.log(JSON.stringify(webpackConfigs)) DEBUG && console.log('============== Webpack Debug End =============') } justDoooooooooooooIt()