vue.config.js 9.75 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
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');
wb-ct393452's avatar
wb-ct393452 committed
13

14 15 16 17
const CompressionWebpackPlugin = require('compression-webpack-plugin');

const productionGzipExtensions = ['js', 'css'];
const isProd = process.env.NODE_ENV === 'production';
wb-ct393452's avatar
wb-ct393452 committed
18 19 20 21

const theme = settingConfig.theme;

function getThemeColors(color, $theme) {
22 23 24 25 26 27 28 29 30 31 32 33 34 35
    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;
wb-ct393452's avatar
wb-ct393452 committed
36 37 38
}

function modifyVars(color) {
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
    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],
    };
wb-ct393452's avatar
wb-ct393452 committed
76 77 78 79
}

// 修正 webpack-theme-color-replacer 插件提取的 css 结果
function resolveCss(output, srcArr) {
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    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]]);
        }
    });
wb-ct393452's avatar
wb-ct393452 committed
95

96 97
    // 去重
    srcArr = dropDuplicate(srcArr);
wb-ct393452's avatar
wb-ct393452 committed
98

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
    // 处理 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);
            }
wb-ct393452's avatar
wb-ct393452 committed
125
        }
126 127 128
    });
    output = outArr.join('\n');
    return output;
wb-ct393452's avatar
wb-ct393452 committed
129 130 131 132
}

// 数组去重
function dropDuplicate(arr) {
133 134 135 136 137 138 139
    let map = {};
    let r = [];
    for (let s of arr) {
        if (!map[s]) {
            r.push(s);
            map[s] = 1;
        }
wb-ct393452's avatar
wb-ct393452 committed
140
    }
141
    return r;
wb-ct393452's avatar
wb-ct393452 committed
142 143 144 145 146 147 148 149 150 151 152 153
}

/**
 * 从字符串解析 css 对象
 * @param cssText
 * @returns {{
 *   name: String,
 *   rules: Array[String],
 *   toText: function
 * }}
 */
function parseCssObj(cssText) {
154 155 156 157 158 159 160 161 162 163 164 165 166 167
    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;
wb-ct393452's avatar
wb-ct393452 committed
168 169 170
}

const assetsCDN = {
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
    // 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',
    ],
wb-ct393452's avatar
wb-ct393452 committed
191 192 193
};

module.exports = {
194
    devServer: {
195 196 197 198 199 200 201 202 203 204 205
        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': ''
                // }
            },
        },
wb-ct393452's avatar
wb-ct393452 committed
206
    },
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
    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<string> 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')],
            },
wb-ct393452's avatar
wb-ct393452 committed
286 287
        },
    },
288 289 290 291
    publicPath: process.env.VUE_APP_PUBLIC_PATH,
    outputDir: 'dist',
    assetsDir: 'static',
    productionSourceMap: false,
wb-ct393452's avatar
wb-ct393452 committed
292
};