let path = require('path'); const webpack = require('webpack'); const ThemeColorReplacer = require('webpack-theme-color-replacer'); const globalConfig = require('./src/config/global.config'); const settingConfig = require('./src/config/setting.config'); const cssResolverConfig = require('./src/config/cssResolver.config'); const { getMenuColors, getAntdColors, getThemeToggleColors, getFunctionalColors, } = require('./src/utils/colorUtil'); const CompressionWebpackPlugin = require('compression-webpack-plugin'); const productionGzipExtensions = ['js', 'css']; const isProd = process.env.NODE_ENV === 'production'; const theme = settingConfig.theme; function getThemeColors(color, $theme) { const _color = color || theme.color; const mode = $theme || theme.mode; const replaceColors = getThemeToggleColors(_color, mode); const themeColors = [ ...replaceColors.mainColors, ...replaceColors.subColors, ...replaceColors.menuColors, ...replaceColors.contentColors, ...replaceColors.rgbColors, ...replaceColors.functionalColors.success, ...replaceColors.functionalColors.warning, ...replaceColors.functionalColors.error, ]; return themeColors; } function modifyVars(color) { let _color = color || theme.color; const palettes = getAntdColors(_color, theme.mode); const menuColors = getMenuColors(_color, theme.mode); const { success, warning, error } = getFunctionalColors(theme.mode); const primary = palettes[5]; return { 'primary-color': primary, 'primary-1': palettes[0], 'primary-2': palettes[1], 'primary-3': palettes[2], 'primary-4': palettes[3], 'primary-5': palettes[4], 'primary-6': palettes[5], 'primary-7': palettes[6], 'primary-8': palettes[7], 'primary-9': palettes[8], 'primary-10': palettes[9], 'info-color': primary, 'success-color': success[5], 'warning-color': warning[5], 'error-color': error[5], 'alert-info-bg-color': palettes[0], 'alert-info-border-color': palettes[2], 'alert-success-bg-color': success[0], 'alert-success-border-color': success[2], 'alert-warning-bg-color': warning[0], 'alert-warning-border-color': warning[2], 'alert-error-bg-color': error[0], 'alert-error-border-color': error[2], 'processing-color': primary, 'menu-dark-submenu-bg': menuColors[0], 'layout-header-background': menuColors[1], 'layout-trigger-background': menuColors[2], 'btn-danger-bg': error[4], 'btn-danger-border': error[4], ...globalConfig.theme[theme.mode], }; } // 修正 webpack-theme-color-replacer 插件提取的 css 结果 function resolveCss(output, srcArr) { let regExps = []; // 提取 resolve 配置中所有的正则配置 Object.keys(cssResolverConfig).forEach(key => { let isRegExp = false; let reg = {}; try { reg = eval(key); isRegExp = reg instanceof RegExp; } catch (e) { isRegExp = false; } if (isRegExp) { regExps.push([reg, cssResolverConfig[key]]); } }); // 去重 srcArr = dropDuplicate(srcArr); // 处理 css let outArr = []; srcArr.forEach(text => { // 转换为 css 对象 let cssObj = parseCssObj(text); // 根据selector匹配配置,匹配成功,则按配置处理 css if (cssResolverConfig[cssObj.selector] != undefined) { let cfg = cssResolverConfig[cssObj.selector]; if (cfg) { outArr.push(cfg.resolve(text, cssObj)); } } else { let cssText = ''; // 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理 for (let regExp of regExps) { if (regExp[0].test(cssObj.selector)) { let cssCfg = regExp[1]; cssText = cssCfg ? cssCfg.resolve(text, cssObj) : ''; break; } // 未匹配到正则,则设置 cssText 为默认的 css(即不处理) cssText = text; } if (cssText != '') { outArr.push(cssText); } } }); output = outArr.join('\n'); return output; } // 数组去重 function dropDuplicate(arr) { let map = {}; let r = []; for (let s of arr) { if (!map[s]) { r.push(s); map[s] = 1; } } return r; } /** * 从字符串解析 css 对象 * @param cssText * @returns {{ * name: String, * rules: Array[String], * toText: function * }} */ function parseCssObj(cssText) { let css = {}; const ruleIndex = cssText.indexOf('{'); css.selector = cssText.substring(0, ruleIndex); const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1); const rules = ruleBody.split(';'); css.rules = rules; css.toText = function() { let body = ''; this.rules.forEach(item => { body += item + ';'; }); return `${this.selector}{${body}}`; }; return css; } const assetsCDN = { // webpack build externals cdn预加载使用 externals: { vue: 'Vue', 'vue-router': 'VueRouter', vuex: 'Vuex', axios: 'axios', nprogress: 'NProgress', clipboard: 'ClipboardJS', '@antv/data-set': 'DataSet', }, css: [], js: [ '//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js', '//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js', '//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js', '//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js', '//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js', '//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js', '//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js', ], }; module.exports = { devServer: { proxy: { '/api': { //此处要与 /services/api.js 中的 API_PROXY_PREFIX 值保持一致 // target: process.env.VUE_APP_API_BASE_URL, target: 'http://platform.kuopu.net:9300', changeOrigin: true, // pathRewrite: { // '^/api': '' // } }, }, }, pluginOptions: { /** * 导入css 预处理器的一些公共的样式文件变量,比如:variables , mixins , functions, * 避免在每个样式文件中手动的@import导入,然后在各个css 文件中直接使用 变量。 * * theme.less 定义的变量预先导入 直接使用 */ 'style-resources-loader': { preProcessor: 'less', //这个是绝对路径,不能使用 alias中配置的别名路径,如@表示的src patterns: [path.resolve(__dirname, './src/theme/default.less')], }, }, configureWebpack: config => { config.entry.app = ['babel-polyfill', 'whatwg-fetch', './src/main.js']; config.performance = { hints: false, }; config.plugins.push( new ThemeColorReplacer({ //Optional. output css file name, suport [contenthash] and [hash]. fileName: 'css/theme-colors-[contenthash:8].css', //matchColors: Array Colors array for extracting css file. //Css rules which have any one of these colors will be extracted out. //抽取所有相关颜色做替换 matchColors: getThemeColors(), //injectCss: boolean Optional. Inject css text into js file, no need to download theme-colors-xxx.css any more. injectCss: true, //resolveCss: Function(resultCss : string) : string Optional. Resolve result css code as you wish. //转换原有CSS中的特定样式为自定义样式 resolveCss, }), ); // Ignore all locale files of moment.js config.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)); // 生产环境下将资源压缩成gzip格式 if (isProd) { // add `CompressionWebpack` plugin to webpack plugins config.plugins.push( new CompressionWebpackPlugin({ algorithm: 'gzip', test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'), threshold: 10240, minRatio: 0.8, }), ); } // if prod, add externals if (isProd) { config.externals = assetsCDN.externals; } }, chainWebpack: config => { // 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突 if (isProd) { config.plugin('optimize-css').tap(args => { args[0].cssnanoOptions.preset[1].colormin = false; return args; }); } // 生产环境下使用CDN if (isProd) { config.plugin('html').tap(args => { args[0].cdn = assetsCDN; return args; }); } }, css: { loaderOptions: { less: { lessOptions: { modifyVars: modifyVars(), javascriptEnabled: true, }, }, postcss: { plugins: [require('tailwindcss'), require('autoprefixer')], }, }, }, publicPath: process.env.VUE_APP_PUBLIC_PATH, outputDir: 'dist', assetsDir: 'static', productionSourceMap: false, };