前端组件库自定义主题切换探索-03-webpack-theme-color-replacer webpack 同时替换多个颜色改造

news2024/11/25 16:52:28

接上一篇《前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-02》
这篇我们来开始改造,让这个插件最终能达到我们的目的:

首先修改plugin.config.js

插件首先要在vue.config.js引用注册,因此先对这里做改造。这里我们指定了四种颜色,primary,danger,warning,other,然后生成配置数据,数据格式如下

{
 primary: {
	 matchColors: getAntdSerials(colorTypes[type]), // colors array for extracting css file, support rgb and hsl.
    fileName: `css/${type}-colors-[contenthash:8].css`, // optional. output css file name, suport [contenthash] and [hash].
    configVar: `tc_cfg_${type}` + Math.random().toString().slice(2),
}
}

后面的代码逻辑都会根据对象的键名(比如primary)来读取每个键名下面的配置来做批量操作

最终代码如下:

const ThemeColorReplacer = require("./webpack-theme-color-replacer/src/index")
// const ThemeColorReplacer = require("webpack-theme-color-replacer")
const generate = require("@ant-design/colors/lib/generate").default

const getAntdSerials = (color) => {
  // 淡化(即less的tint)
  const lightens = new Array(9).fill().map((t, i) => {
    return ThemeColorReplacer.varyColor.lighten(color, i / 10)
  })
  // console.log("lightens", lightens)
  const colorPalettes = generate(color)
  // console.log("colorPalettes", colorPalettes)
  const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace("#", "")).join(",")
  // console.log("rgb", rgb)
  const matchColors = lightens.concat(colorPalettes).concat(rgb)
  // console.log("matchColors", matchColors)
  return matchColors
}
const getRandomString = () => Math.random().toString().slice(2)
const colorTypes = {
  primary: "#1890ff",
  danger: "#F5222D",
  warning: "#F2A830",
  other: "#35964f",
}
const option = {}
for (const type in colorTypes) {
  option[type] = {
    matchColors: getAntdSerials(colorTypes[type]), // colors array for extracting css file, support rgb and hsl.
    fileName: `css/${type}-colors-[contenthash:8].css`, // optional. output css file name, suport [contenthash] and [hash].
    configVar: `tc_cfg_${type}` + getRandomString(),
  }
}
const createThemeColorReplacerPlugin = () => new ThemeColorReplacer({
  option
})

module.exports = createThemeColorReplacerPlugin

在这个验证测试代码过程中,也遇到了不少问题,这里为了省事点,我们就不赘述中间的曲折,直接上改好的代码,后面再附加一些注意的地方

第二个改scr下面的index.js

这个文件主要是根据option生成LC_THEME_CONFIG变量里面的内容,关键代码是

      const { option } = this.handler.options
      for (const i in option) {
        webpackDefineConfig[i] = option[i].configVar
      }
        new webpack.DefinePlugin({
          LC_THEME_CONFIG: JSON.stringify(webpackDefineConfig)
        }).apply(compiler)

完整的代码如下:

"use strict"
var Handler = require("./handler")

var webpack = require("webpack")

class ThemeColorReplacer {
    constructor(options) {
        this.handler = new Handler(options)
    }

    getBinder(compiler, event) {
        return compiler.hooks
            ? compiler.hooks[event].tapAsync.bind(compiler.hooks[event], "ThemeColorReplacer")
            : compiler.plugin.bind(compiler, event)
    }

    apply(compiler) {
      const webpackDefineConfig = {}
      const { option } = this.handler.options
      for (const i in option) {
        webpackDefineConfig[i] = option[i].configVar
      }
        new webpack.DefinePlugin({
          LC_THEME_CONFIG: JSON.stringify(webpackDefineConfig)
        }).apply(compiler)

      compiler.hooks.thisCompilation.tap("ThemeColorReplacer", (compilation) => {
        compilation.hooks.processAssets.tapAsync(
            {
              name: "ThemeColorReplacer",
              stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
            },
            (compilationAssets, callback) => {
              this.handler.handle(compilation)
              callback()
            })
      })
    }
}

ThemeColorReplacer.varyColor = require("../client/vary-color")

module.exports = ThemeColorReplacer

第三改造index.js直接引用的Handler.js

这里需要改造的关键地方是

        const { option } = this.options
        // let { injectToHtml } = this.options
        for (const i in option) {
            const { fileName, matchColors, configVar } = option[i]
            const output = this.assetsExtractor.extractAssets(compilation.assets, matchColors, i)
            // console.log("handle output", output)
            // console.log("Extracted theme color css content length: " + output.length)
            const outputName = compilation.getPath(replaceFileName(fileName, output), {})
            this.emitSource(compilation, outputName, new wpSources.RawSource(output))
            // console.log("fileName", fileName)
            // console.log("matchColors", matchColors)
            // 记录动态的文件名,到每个入口js
            // console.log("outputName", outputName)
            this.addToEntryJs(outputName, compilation, output, matchColors, configVar)
        }

另外,其他地方需要传递matchColors和configVar,之前因为只改一种颜色,所以是直接用最外面传递进来的,现在要改变多个则需要在for循环里面传递,最终修改代码如下:

"use strict"
var webpack = require("webpack")
var AssetsExtractor = require("./assets-extractor")
var replaceFileName = require("./replace-file-name")
var LineReg = /\n/g
var wpSources = webpack.sources
if (!wpSources) {
    wpSources = require("webpack-sources") // for webpack 4
}
module.exports = class Handler {
    constructor(options) {
        this.options = {
            isJsUgly: !(process.env.NODE_ENV === "development" || process.argv.find(arg => arg.match(/\bdev/))),
            ...options
        }
        this.assetsExtractor = new AssetsExtractor(this.options.isJsUgly, this.options.changeSelector)
    }

    // Add Webpack5 Support
    emitSource(compilation, name, source) {
        console.log("emitSource name", name)
        var exists = compilation.assets[name]
        if (compilation.updateAsset) { // webpack.version[0] >= '5'
            if (exists) compilation.updateAsset(name, source)
            else compilation.emitAsset(name, source)
        } else {
            if (exists) delete compilation.assets[name]
            compilation.assets[name] = source
        }
    }

    handle(compilation) {
        // Add to assets for output
        const { option } = this.options
        // let { injectToHtml } = this.options
        for (const i in option) {
            const { fileName, matchColors, configVar } = option[i]
            const output = this.assetsExtractor.extractAssets(compilation.assets, matchColors, i)
            // console.log("handle output", output)
            // console.log("Extracted theme color css content length: " + output.length)
            const outputName = compilation.getPath(replaceFileName(fileName, output), {})
            this.emitSource(compilation, outputName, new wpSources.RawSource(output))
            // console.log("fileName", fileName)
            // console.log("matchColors", matchColors)
            // 记录动态的文件名,到每个入口js
            // console.log("outputName", outputName)
            this.addToEntryJs(outputName, compilation, output, matchColors, configVar)
        }
    }

// 自动注入js代码,设置css文件名
    addToEntryJs(outputName, compilation, cssCode, matchColors, configVar) {
        const onlyEntrypoints = {
            entrypoints: true,
            errorDetails: false,
            modules: false,
            assets: false,
            children: false,
            chunks: false,
            chunkGroups: false
        }
        const entrypoints = compilation.getStats().toJson(onlyEntrypoints).entrypoints
        Object.keys(entrypoints).forEach(entryName => {
            const entryAssets = entrypoints[entryName].assets
            for (let i = 0, l = entryAssets.length; i < l; i++) {
                const assetName = entryAssets[i].name || entryAssets[i]
                if (assetName.slice(-3) === ".js" && assetName.indexOf("manifest.") === -1) { //
                    const assetSource = compilation.assets[assetName]
                    if (assetSource && !assetSource._isThemeJsInjected) {
                        const cSrc = this.getEntryJs(outputName, assetSource, cssCode, matchColors, configVar)
                        // cSrc._isThemeJsInjected = true
                        this.emitSource(compilation, assetName, cSrc)
                        break
                    }
                }
            }
        })
    }

    getConfigJs(outputName, cssCode, matchColors, configVar) {
        const config = { url: outputName, colors: matchColors }
        if (this.options.injectCss) {
            config.cssCode = cssCode.replace(LineReg, "")
        }
        return "\n(typeof window=='undefined'?global:window)." + configVar + "=" + JSON.stringify(config) + ";\n"
    }

    getEntryJs(outputName, assetSource, cssCode, matchColors, configVar) {
        const ConcatSource = wpSources.ConcatSource
        const CachedSource = wpSources.CachedSource
        const configJs = this.getConfigJs(outputName, cssCode, matchColors, configVar)
        if (assetSource instanceof CachedSource) { // CachedSource没有node方法,会报错
            return new CachedSource(concatSrc(assetSource._source || assetSource.source(), configJs))
        }
        return concatSrc(assetSource, configJs)

        function concatSrc(assetSource, configJs) {
            if (assetSource instanceof ConcatSource) {
                assetSource.add(configJs)
                return assetSource
            } else {
                return new ConcatSource(assetSource, configJs)
            }
        }
    }
}

这里需要注意的是,在addToEntryJs函数里面,需要吧cSrc._isThemeJsInjected = true去掉。这里是之前插件为了做缓存用的,在测试中发现,加上这一行,window里面最多挂两个tc_cfg_变量,
在这里插入图片描述
在原本的判断里面,执行cSrc._isThemeJsInjected = true时,assetSource._isThemeJsInjected 也会变为true,因为this.getEntryJs返回的是一个浅拷贝,且和assetSource同源,从下面的代码可以看到数据的来源
在这里插入图片描述
在这里插入图片描述
数据最终通过webpack处理而来,至于为什么是浅拷贝,是同源的,没有得去深究。

replace-file-name 不需要更改,源代码即可。

第四需要改的是assets-extractor.js

(原插件是AssetsExtractor,这里为了符合开发规范改了名字)文件。这里没有大改的地方,而是将参数改为从调用的地方传递,而不是直接取自option,改动后代码如下:

var Extractor = require("./extractor")
var cssLoaderRegDev = /\bn?(?:exports|___CSS_LOADER_EXPORT___)\.push\(\[module\.id?, \\?"(.+?\})(?:\\?\\n)?(?:[\\n]*\/\*#\s*sourceMappingURL=.+?\*\/)?\\?", \\?"\\?"(?:\]\)|,\s*\{)/g

// css-loader:  n.exports=t("FZ+f")(!1)).push([n.i,"\n.payment-type[data-v-ffb10066] {......}\n",""])
var cssLoaderRegUgly = /\.push\(\[\w+\.i,['"](.+?\})[\\rn]*['"],['"]['"](?:\]\)|,\{)/g
var CssExtReg = /\.css$/i; var JsExtReg = /\.js$/i
function assetToStr(asset) {
    var src = asset.source() || ""
    return src.toString()
}
const extractAsset = function (fn, asset, matchColors, isJsUgly, changeSelector) {
    const src = assetToStr(asset)
    var cssSrcs = []
    var CssCodeReg = isJsUgly ? cssLoaderRegUgly : cssLoaderRegDev
    src.replace(CssCodeReg, (match, $1) => {
        cssSrcs = cssSrcs.concat(Extractor(changeSelector, $1, matchColors))
    })
    // console.log("cssSrcs", cssSrcs.filter(item=>item))
    return cssSrcs.filter(item => item)
}
function extractAll(assets, matchColors, isJsUgly, changeSelector, type) {
    // console.log("extractAll matchColors", matchColors)
    var cssSrcs = []
    Object.keys(assets).map(fn => {
        // 原本每修改一点源码,都需要对整个项目的assets翻一遍css,影响性能。
        // 故改为在asset上缓存上一次的结果,对没发生变化的asset直接取缓存(已发生变化的asset已经是新对象,无缓存)。
        // console.log("fn", fn)
        const asset = assets[fn]
        // console.log("asset._themeCssCache", asset._themeCssCache)
        let cssRules = ""
        if (asset._themeCssCache && asset._themeCssCache[type]) {
            cssRules = asset._themeCssCache[type]
        } else {
            cssRules = extractAsset(fn, asset, matchColors, isJsUgly, changeSelector)
        }
        // asset._themeCssCache || extractAsset(fn, asset, matchColors, isJsUgly, changeSelector)
        // console.log("cssRules", cssRules)
        if (asset._themeCssCache) {
            asset._themeCssCache[type] = cssRules
        } else {
            asset._themeCssCache = {
                [type]: cssRules
            }
        }

        cssSrcs = cssSrcs.concat(cssRules)
    })
    // console.log("cssSrcs", cssSrcs)
    // console.log("cssSrcs.filter(item => item)", cssSrcs.filter(item => item))
    return cssSrcs
}

module.exports = function AssetsExtractor(isJsUgly, changeSelector) {
    this.extractAssets = function (assets, matchColors, type) {
        // console.log("assets", assets)
        var srcArray = this.extractToArray(assets, matchColors, type)
        // console.log("srcArray", srcArray)
        // 外部的css文件。如cdn加载的

        var output = dropDuplicate(srcArray).join("\n")
        return output
    }
    this.extractToArray = function (assets, matchColors, type) {
        var srcArray = extractAll(assets, matchColors, isJsUgly, changeSelector, type)
        // console.log("srcArray------------------------------------------------------", srcArray)
        if (srcArray.length === 0 && !this._uglyChanged) {
            // 容错一次
            this._uglyChanged = true
            isJsUgly = !isJsUgly
            // 清空缓存
            Object.keys(assets).map(fn => assets[fn]._themeCssCache = 0)
            srcArray = extractAll(assets, matchColors, isJsUgly, changeSelector, type)
        }
        return srcArray
    }
}

function dropDuplicate(arr) {
    var map = {}
    var r = []
    for (var s of arr) {
        if (!map[s]) {
            r.push(s)
            map[s] = 1
        }
    }
    return r
}

这里需要注意的一点是,asset._themeCssCache 需要按照外面传递的类型来存储,否则后面的颜色不生效

        const asset = assets[fn]
        // console.log("asset._themeCssCache", asset._themeCssCache)
        let cssRules = ""
        if (asset._themeCssCache && asset._themeCssCache[type]) {
            cssRules = asset._themeCssCache[type]
        } else {
            cssRules = extractAsset(fn, asset, matchColors, isJsUgly, changeSelector)
        }
        // asset._themeCssCache || extractAsset(fn, asset, matchColors, isJsUgly, changeSelector)
        // console.log("cssRules", cssRules)
        if (asset._themeCssCache) {
            asset._themeCssCache[type] = cssRules
        } else {
            asset._themeCssCache = {
                [type]: cssRules
            }
        }

第五需要改的是assets-extractor.js直接引用的extractor.js

这里需要改动的是将matchColors从外部直接传入,而不是从option取用
改动后代码如下

var extractorCss = require("./css-extractor")
const testCssCode = function (cssCode, matchColors) {
    var matchColorRegs = matchColors // ['#409EFF', '#409eff', '#53a8ff', '#66b1ff', '#79bbff', '#8cc5ff', '#a0cfff', '#b3d8ff', '#c6e2ff', '#d9ecff', '#ecf5ff', '#3a8ee6', '#337ecc']
        .map(c => new RegExp(c.replace(/\s/g, "").replace(/,/g, ",\\s*") + "([\\da-f]{2})?(\\b|\\)|,|\\s)", "i")) // 255, 255,3
    for (var colorReg of matchColorRegs) {
        if (colorReg.test(cssCode)) return true // && !ExclueCssReg.test(cssCode)
    }
    return false
}
module.exports = function Extractor(changeSelector, src, matchColors) {
    return extractorCss(src, changeSelector).map(function (css) {
        var rules = css.rules.filter(cssCode => testCssCode(cssCode, matchColors))
        if (!rules.length) return ""
        return css.selector + "{" + rules.join(";") + "}"
    })
}

第六个需要改的是extractor.js直接引用的css-extractor.js

同样是需要改动传参方式,最终代码如下

// \n和备注
var regLfRem = /\\\\?n|\n|\\\\?t|\\\\?r|\/\*[\s\S]+?\*\//g

var SpaceReg = /\s+/g
var TrimReg = /(^|,)\s+|\s+($)/g // 前空格,逗号后的空格; 后空格
var SubCssReg = /\s*>\s*/g // div > a 替换为 div>a
var DataUrlReg = /url\s*\([\\'"\s]*data:/ // url("data:image/svg+xml;base64,PHN2")
var QuotReg = /\\+(['"])/g
// var ExclueCssReg = /(?:scale3d|translate3d|rotate3d|matrix3d)\s*\(/i;
module.exports = function extractCss(src, changeSelector) {
    src = src.replace(regLfRem, "")
    var ret = []
    var nameStart; var nameEnd; var cssEnd = -1
    while (true) {
        nameStart = cssEnd + 1
        nameEnd = src.indexOf("{", nameStart)
        cssEnd = findCssEnd(src, nameEnd)
        if (cssEnd > -1 && cssEnd > nameEnd && nameEnd > nameStart) {
            var cssCode = src.slice(nameEnd + 1, cssEnd)
            if (cssCode.indexOf("{") > -1) { // @keyframes
                var rules = extractCss(cssCode, changeSelector)
            } else {
                rules = getRules(cssCode)
            }
            if (rules.length) {
                var selector = src.slice(nameStart, nameEnd)
                selector = selector.replace(TrimReg, "$1")
                selector = selector.replace(SubCssReg, ">")
                selector = selector.replace(SpaceReg, " ") // lines
                var p = selector.indexOf(";") // @charset utf-8;
                if (p > -1) {
                    selector = selector.slice(p + 1)
                }
                // 改变选择器
                if (changeSelector) {
                    var util = {
                        rules: rules,
                        changeEach: changeEach
                    }
                    selector = changeSelector(selector.split(",").sort().join(","), util) || selector
                }
                ret.push({ selector, rules: rules })
            }
        } else {
            break
        }
    }
    return ret

    // 查找css尾部,兼容 @keyframes {10%{...}}
    function findCssEnd(src, start) {
        var level = 1
        var cssEnd = start
        while (true) {
            cssEnd++
            var char = src[cssEnd]
            if (!char) {
                return -1
            } else if (char === "{") {
                level++
            } else if (char === "}") {
                level--
                if (level === 0) {
                    break
                }
            }
        }
        return cssEnd
    }

    function changeEach(selector, surfix, prefix) {
        surfix = surfix || ""
        prefix = prefix || ""
        return selector.split(",").map(function (s) {
            return prefix + s + surfix
        }).join(",")
    }
}

function getRules(cssCode) {
    var rules = cssCode.split(";")
    var ret = []
    for (var i = 0; i < rules.length; i++) {
        var rule = rules[i].replace(/^\s+|\s+$/, "")
        if (!rule) continue
        if (rule.match(DataUrlReg)) {
            rule += ";" + rules[i + 1]
            rule = rule.replace(QuotReg, "$1")
            i++
        }
        ret.push(rule.replace(SpaceReg, " "))
    }
    return ret
}

现在src下面的文件更改完了,我们先去改调用处的vue文件。

第七是路由文件theme-example.vue

<template>
  <basic-container>
    <div>
      <a-button type="primary">主色-primary</a-button>
      <a-button type="danger">报错色-danger</a-button>
      <span class="my-theme-color">测试自定义主题色</span>
      <span class="my-warning-color">测试自定义警告色</span>
      <span class="my-other-color">测试自定义其他颜色</span>
    </div>
    <setting-drawer ref="settingDrawer"/>
  </basic-container>
</template>
<script lang="ts">
import BasicContainer from "../../components/layouts/basic-container2.vue"
import { Component, Vue } from "vue-property-decorator"
import SettingDrawer from "../../../packages/setting-drawer"

@Component({
  components: {
    BasicContainer,
    SettingDrawer
  },
})
export default class ThemeExample extends Vue {

}
</script>
<style scoped lang="less">
.my-theme-color{
  color: #1890ff;
}
.my-warning-color{
  color: #F2A830;
}
.my-other-color{
  color: #35964f;
}
</style>

第八是setting-drawer.vue,至于basic-container2.vue,就是一个容器文件,先不去管

<template>
  <div class="setting-drawer">
    <div class="setting-drawer-index-content">
      <div :style="{ marginTop: '24px' }">
        <h3 class="setting-drawer-index-title">切换颜色列表</h3>
        <div>
          <a-tooltip class="setting-drawer-theme-color-colorBlock" v-for="(item, index) in colorList" :key="index">
            <template slot="title">
              {{ item.key }}
            </template>
            <a-tag :color="item.color" @click="changeColor(item.color,index)">
              <a-icon type="check" v-if="item.color === color"></a-icon>
              <a-icon type="check" style="color: transparent;" v-else></a-icon>
            </a-tag>
          </a-tooltip>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { updateTheme, colorList } from "./settingConfig"

export default {
  data () {
    return {
      colorList,
      color: "",
    }
  },
  methods: {
    changeColor (color, index) {
      updateTheme({
        primary: color,
        danger: this.colorList[index + 1] ? this.colorList[index + 1].color : this.colorList[0].color,
        warning: this.colorList[index + 2] ? this.colorList[index + 2].color : (this.colorList[index + 1] ? this.colorList[0].color : this.colorList[1].color),
        other: this.colorList[index + 3] ? this.colorList[index + 3].color : (this.colorList[index + 2] ? this.colorList[index + 2].color : this.colorList[index + 1] ? this.colorList[0].color : this.colorList[2].color),
      })
    },
  }
}
</script>

第九需要改动的是setting-drawer.vue直接引用的settingConfig.js

import themeColor from "./themeColor.js"
const colorList = [
  {
    key: "薄暮", color: "#F5222D"
  },
  {
    key: "火山", color: "#FA541C"
  },
  {
    key: "日暮", color: "#FAAD14"
  },
  {
    key: "明青", color: "#13C2C2"
  },
  {
    key: "极光绿", color: "#52C41A"
  },
  {
    key: "拂晓蓝(默认)", color: "#1890FF"
  },
  {
    key: "极客蓝", color: "#2F54EB"
  },
  {
    key: "酱紫", color: "#722ED1"
  },
  {
    key: "浅紫", color: "#9890Ff"
  }
]

const updateTheme = (changeColors) => {
  // no-undef
  themeColor.changeColor(changeColors).finally(() => {
  })
}

export { updateTheme, colorList }

第十需要改动的是settingConfig.js直接引用的themeColor.js

import client from "../../../config/webpack-theme-color-replacer/client"
import generate from "@ant-design/colors/lib/generate"

export default {
  /**
   * 获取变化的颜色
   * @param color
   * @returns {T[]}
   */
  getAntdSerials (color) {
    // 淡化(即less的tint)
    const lightens = new Array(9).fill().map((t, i) => {
      return client.varyColor.lighten(color, i / 10)
    })
    // colorPalette变换得到颜色值
    // console.log("lightens", lightens)
    const colorPalettes = generate(color)
    // console.log("colorPalettes", colorPalettes)
    const rgb = client.varyColor.toNum3(color.replace("#", "")).join(",")
    // console.log("rgb", rgb)
    return lightens.concat(colorPalettes).concat(rgb)
  },
  /**
   * 改变颜色
   * @param changeColors
   * @returns {Promise<unknown>|Promise<unknown>}
   */
  changeColor (changeColors) {
    const options = {}
    for (const i in changeColors) {
      options[i] = {
        newColors: this.getAntdSerials(changeColors[i]),
        changeUrl (cssUrl) {
          return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
        }
      }
    }
    return client.changer.changeColor(options)
  }
}

这里的关键改动是根据我们的需要组装插件需要的数据源,关键代码如下:

    for (const i in changeColors) {
      options[i] = {
        newColors: this.getAntdSerials(changeColors[i]),
        changeUrl (cssUrl) {
          return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
        }
      }
    }

第十一需要改动的是theme-color-changer.js
这里的关键代码是将themeColorConfig按照类型存储,和按照类型从LC_THEME_CONFIG和window中取用数据,关键代码如下:

    for (const i in option) {
      if (!themeColorConfig[i]) {
        // eslint-disable-next-line no-undef
        console.log("LC_THEME_CONFIG[i]", LC_THEME_CONFIG[i])
        // eslint-disable-next-line no-undef
        themeColorConfig[i] = win()[LC_THEME_CONFIG[i]]
        const later = retry(i)
        // 重试直到themeColorConfig加载
        console.log("later", later)
        if (later) return later
      }
      const { oldColors, newColors, cssUrl, changeUrl } = option[i]
      oldColorsObj[i] = oldColors || themeColorConfig[i].colors || []
      newColorsObj[i] = newColors || []
      const cssUrlValue = themeColorConfig[i].url || cssUrl
      cssUrlObj[i] = changeUrl ? changeUrl(cssUrlValue) : cssUrlValue // url可能被changeUrl改变
    }

这里的option就是注册插件时传递的option,即代码const createThemeColorReplacerPlugin = () => new ThemeColorReplacer({
option
})。
另外 oldColorsObj ,newColorsObj ,cssUrlObj 也需要改为对象形式,需要按照键名存储

其他改动后的完整代码如下:

const _urlColors = {} // {[url]: {id,colors}}
const themeColorConfig = {}

module.exports = {
  _tryNum: 0,
  _tryNumObj: {},
  changeColor: function (option) {
    const _this = this
    const oldColorsObj = {}
    const newColorsObj = {}
    const cssUrlObj = {}
    console.log("wen()", win())
    // eslint-disable-next-line no-undef
    console.log("LC_THEME_CONFIG", LC_THEME_CONFIG)
    console.log("option", option)
    for (const i in option) {
      if (!themeColorConfig[i]) {
        // eslint-disable-next-line no-undef
        console.log("LC_THEME_CONFIG[i]", LC_THEME_CONFIG[i])
        // eslint-disable-next-line no-undef
        themeColorConfig[i] = win()[LC_THEME_CONFIG[i]]
        const later = retry(i)
        // 重试直到themeColorConfig加载
        console.log("later", later)
        if (later) return later
      }
      const { oldColors, newColors, cssUrl, changeUrl } = option[i]
      oldColorsObj[i] = oldColors || themeColorConfig[i].colors || []
      newColorsObj[i] = newColors || []
      const cssUrlValue = themeColorConfig[i].url || cssUrl
      cssUrlObj[i] = changeUrl ? changeUrl(cssUrlValue) : cssUrlValue // url可能被changeUrl改变
    }
    console.log("themeColorConfig", themeColorConfig)

    return new Promise(function (resolve, reject) {
      const optionKeys = Object.keys(option || {})
      const isSameArrReturn = optionKeys.every(key => isSameArr(oldColorsObj[key], newColorsObj[key])) // 判断是不是所有的都相同
      // console.log("isSameArrReturn", isSameArrReturn)
      if (isSameArrReturn) {
        resolve()
      } else {
        for (const i in option) {
          const last = _urlColors[cssUrlObj[i]]
          if (last) {
            // 之前已替换过
            oldColorsObj[i] = last.colors
          }
          if (!isSameArr(oldColorsObj[i], newColorsObj[i])) {
            setCssText(last, cssUrlObj[i], oldColorsObj[i], newColorsObj[i], i, resolve, reject)
          }
        }
      }
    })

    function retry(type) {
      if (!themeColorConfig[type]) {
        if (_this._tryNumObj[type] < 9) {
          _this._tryNumObj[type] = _this._tryNumObj[type] + 1
          return new Promise(function (resolve, reject) {
            setTimeout(function () {
              resolve(_this.changeColor(option))
            }, 100)
          })
        } else {
          themeColorConfig[type] = {}
        }
      }
    }

    function setCssText(last, url, oldColors, newColors, type, resolve, reject) {
      // console.log("last=", last, ",url=", url, ",oldColors=", oldColors, ",newColors=", newColors, ",type=", type,)
      let elStyle = last && document.getElementById(last.id)
      if (elStyle && last.colors) {
        setCssTo(elStyle.innerText)
        last.colors = newColors
        resolve()
      } else {
        // 第一次替换
        const id = "css_" + type + (+new Date())
        console.log("第一次替换")
        console.log("id", id)
        elStyle = document.querySelector(option.appendToEl || "body")
            .appendChild(document.createElement("style"))
        // console.log("elStyle", elStyle)

        elStyle.setAttribute("id", id)
        // console.log("url", url)
        _this.getCssString(url, function (cssText) {
          // console.log("cssText", cssText)
          setCssTo(cssText)
          _urlColors[url] = { id: id, colors: newColors }
          resolve(cssText)
        }, reject)
      }

      function setCssTo(cssText) {
        cssText = _this.replaceCssText(cssText, oldColors, newColors)
        elStyle.innerText = cssText
      }
    }
  },
  replaceCssText: function (cssText, oldColors, newColors) {
    oldColors.forEach(function (color, t) {
      // #222、#222223、#22222350、222, 255,3 => #333、#333334、#33333450、211,133,53、hsl(27, 92.531%, 52.745%)
      const reg = new RegExp(color.replace(/\s/g, "").replace(/,/g, ",\\s*") + "([\\da-f]{2})?(\\b|\\)|,|\\s)", "ig")
      cssText = cssText.replace(reg, newColors[t] + "$1$2") // 255, 255,3
    })
    return cssText
  },
  getCssString: function (url, resolve, reject) {
    // console.log("url", url)
    const xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(xhr.responseText)
        } else {
          reject(xhr.status)
        }
      }
    }
    xhr.onerror = function (e) {
      reject(e)
    }
    xhr.ontimeout = function (e) {
      reject(e)
    }
    xhr.open("GET", url)
    xhr.send()
  },
}
function win() {
  return typeof window === "undefined" ? global : window
}
function isSameArr(oldColors, newColors) {
  if (oldColors.length !== newColors.length) {
    return false
  }
  for (let i = 0, j = oldColors.length; i < j; i++) {
    if (oldColors[i] !== newColors[i]) {
      return false
    }
  }
  return true
}

到这里就结束了,vary-color.js不需要改动。然后我们运行后看下效果(注意每次改动均需重启项目)

颜色批量替换演示

我们可以看到,页面上成功同时替换了4种颜色。到此这个测试完成。但是我们在页面上实现让用户自定义各种颜色的期望能实现吗?
答案是No!!
虽然这不是我们想要的答案,但是还是要接受目前的现实。下面讲下原因:
1、webpack-theme-color-replacer 这个插件只会根据颜色色号去替换,也就是说如果用户自己在主题色和危险色上选了相同的颜色,那么出现的结果会是项目上的主题色和危险色都被同时更改,并且分不开了,除非用户重置。而且如果我们修改css查找正则,比如只找css属性名带primary的,又会导致没有用ant-design样式的颜色不会被替换
2、其他的比如圆角等的修改,估计会波及无辜。因为比如 border-radius: 2px,这样的如果按照目前插件的替换逻辑,只会替换2px,会殃及一大片无辜的样式,就算按照规则改成替换border-radius: 2px整个字符,也会殃及不少的无辜样式。所以并不现实。

所以,好想赶紧升级到vue3啊,可以很爽快的使用css变量来实现这些逻辑!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373538.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NCRE计算机等级考试Python真题(一)

第一套试题1、关于数据的存储结构&#xff0c;以下选项描述正确的是A.数据所占的存储空间量B.数据在计算机中的顺序存储方式C.数据的逻辑结构在计算机中的表示D.存储在外存中的数据正确答案&#xff1a; C2、关于线性链表的描述&#xff0c;以下选项中正确的是A.存储空间不一定…

js:入门web component

什么是web component 首先丢几个网站 MDN 阮一峰 caniuse 现代 web 前端离不开组件&#xff0c;而我们平常写组件都是基于 react、vue 等框架来写的&#xff0c;web component 则是浏览器原生的组件&#xff0c;就意味着我们可以在浏览器直接运行。 实例 <!DOCTYPE html&…

sql的执行顺序

一.前言 在我们世家开发中,我们少不了和数据库打交道, 我们的持久层是与数据库打交道的, 少不了要用sql语句来请求数据库的数据, 前台(前端页面)请求到-->控制器(接口层)-->service(业务层)-->mapper或dao(持久层) 简图: 在持久层我们的sql是怎么执行的, 它的执行顺…

Springdoc Swagger UI集成OAuth2认证

目录引言方式1&#xff1a;Bearer Token方式2&#xff1a;标准OAuth2授权码流程方式3&#xff1a;集成OIDC发现端点扩展&#xff1a;同时支持多种认证方式引言 之前的文章讲过OAuth2体系&#xff0c;以授权码流程为例&#xff08;参见下图&#xff09;&#xff0c; 其中资源服…

JVM全面总结

JVM全面总结一.类加载子系统why 为什么要这么做&#xff1f;when 什么时候会触发加载How 怎么样进行的---加载相关---类加载器双亲委派机制沙箱安全机制---链接过程相关------初始化相关---类构造器clinit()二.运行时数据区1.方法区(永久代 元空间)(1)方法区在哪&#xff1f;(2…

数据结构基础之动态数组

目录 前言 1、Java中的数组 2、实现动态数组 2.1、基本类结构设计 2.2、添加元素 2.3、查询&修改元素 2.4、包含&搜索&删除 2.5、数组扩容 前言 今天我们来学习一下关于数据结构的一些基础知识&#xff0c;数据结构研究的是数据如何在计算机中进行组织和存…

Java高级点的知识

Java 集合框架 该框架必须是高性能的。基本集合&#xff08;动态数组&#xff0c;链表&#xff0c;树&#xff0c;哈希表&#xff09;的实现也必须是高效的。 该框架允许不同类型的集合&#xff0c;以类似的方式工作&#xff0c;具有高度的互操作性。 对一个集合的扩展和适应…

WordPress 函数:add_theme_support() 开启主题自定义功能(全面)

add_theme_support() 用于在我们的当前使用的主题添加一些特殊的功能&#xff0c;函数一般写在主题的functions.php文件中&#xff0c;当然也可以再插件中使用钩子来调用该函数&#xff0c;如果是挂在钩子上&#xff0c;那他必须挂在after_setup_theme钩子上&#xff0c;因为 i…

Spring Security OAuth2四种授权模式总结 - Mysql存储客户端信息和令牌(八)

写在前面&#xff1a;各位看到此博客的小伙伴&#xff0c;如有不对的地方请及时通过私信我或者评论此博客的方式指出&#xff0c;以免误人子弟。多谢&#xff01;如果我的博客对你有帮助&#xff0c;欢迎进行评论✏️✏️、点赞&#x1f44d;&#x1f44d;、收藏⭐️⭐️&#…

Vue3 核心模块源码解析(上)

Vue3相比大家也都有所了解&#xff0c;即使暂时没有使用上&#xff0c;但肯定也学习过&#xff01;Vue3是使用TS进行重写&#xff0c;采用了MonoRepo的管理方式进行管理&#xff0c;本篇文章我们一起来看看 Vue3的使用&#xff0c;与Vue2有什么区别&#xff0c;以及我们该如何优…

【密码学】 一篇文章讲透数字证书

【密码学】 一篇文章讲透数字证书 数字证书介绍 数字证书是一种用于认证网络通信中参与者身份和加密通信的证书&#xff0c;人们可以在网上用它来识别对方的身份。 我们在上一篇博客中介绍了数字签名的作用和原理&#xff0c;数字签名可以防止消息被否认。有了公钥算法和数字签…

史上最全面的软件测试面试题总结(接口、自动化、性能全都有)

目录 思维发散 Linux 测试概念和模型 测试计划与工具 测试用例设计 Web项目 Python基础 算法 逻辑 接口测试 性能测试 总结感谢每一个认真阅读我文章的人&#xff01;&#xff01;&#xff01; 重点&#xff1a;配套学习资料和视频教学 思维发散 一个球&#xff…

二叉树——二叉搜索树的最小绝对差

二叉搜索树的最小绝对差 链接 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输出&#xff1a;1 示例 2&…

PowerDesigned16连接Oracle出现“Could not initialize JavaVM“时的解决步骤

PowerDesigned需要连接到数据库&#xff0c;我使用的是oracle&#xff0c;但总是连接不上&#xff0c;输出栏提示"Could not initialize JavaVM"。 经过查找资料&#xff0c;发现是PowerDesigned16是32位的&#xff0c;只能使用32位的JDK来运行JDBC驱动&#xff0c;…

如何从零开始系统的学习项目管理?

经常会有人问&#xff0c;项目管理到底应该学习一些什么&#xff1f;学习考证之后能得到什么价值&#xff1f; 以下我就总结一下内容 一&#xff0c;学习项目管理有用吗&#xff1f; 有效的项目管理带来的益处大致包括以下几个方面&#xff1a;更有效达成业务目标、满足相关…

人工智能轨道交通行业周刊-第35期(2023.2.20-2.26)

本期关键词&#xff1a;重庆智慧轨道、智能运维主机、标准轨距、地方铁路公报、景深、机器视觉应用 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通Rai…

第12天-商品维护(发布商品、商品管理、SPU管理)

1.发布商品流程 发布商品分为5个步骤&#xff1a; 基本信息规格参数销售属性SKU信息保存完成 2.发布商品-基本信息 2.1.会员等级-会员服务 2.1.1.会员服务-网关配置 在网关增加会员服务的路由配置 - id: member_routeuri: lb://gmall-memberpredicates:- Path/api/member/…

学习python第一天---前缀和

一、3956.截断数组&#xff08;前缀和&#xff09;二、前缀和&#xff08;前缀和&#xff09;[0]list(map(int,input().split()))三、子矩阵的和&#xff08;前缀和&#xff09;range(1,n1)四、K倍区间&#xff08;前缀和&#xff09;五、激光炸弹&#xff08;前缀和&#xff0…

模型部署笔记

目录模型部署工作ONNX存在的意义ONNX&#xff08;Open Neural Network Exchange&#xff09;ONNX示例模型推理示例Batch调整量化量化方式常见问题模型部署工作 训练好的模型在特定软硬件平台下推理针对硬件优化和加速的推理代码 训练设备平台&#xff1a; CPU、GPU、DSP ONN…

2023.02.26 学习周报

文章目录摘要文献阅读1.题目2.摘要3.介绍4.模型4.1 SESSION-PARALLEL MINI-BATCHES4.2 SAMPLING ON THE OUTPUT4.3 RANKING LOSS5.实验5.1 数据集5.2 验证方式5.3 baselines5.4 实验结果6.结论深度学习元胞自动机1.定义2.构成3.特性4.思想5.统计特征流形学习1.降维2.空间3.距离…