前端组件库自定义主题切换探索-03-webpack-theme-color-replacer webpack 同时替换多个颜色改造
接上一篇《前端组件库自定义主题切换探索-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").defaultconst 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.optionsfor (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.optionsfor (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.optionsfor (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 SupportemitSource(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 outputconst { option } = this.options// let { injectToHtml } = this.optionsfor (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).entrypointsObject.keys(entrypoints).forEach(entryName => {const entryAssets = entrypoints[entryName].assetsfor (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 = truethis.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.ConcatSourceconst CachedSource = wpSources.CachedSourceconst 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 : cssLoaderRegDevsrc.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 = trueisJsUgly = !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,3for (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]+?\*\//gvar 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 = -1while (true) {nameStart = cssEnd + 1nameEnd = 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) { // @keyframesvar 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, " ") // linesvar 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 = 1var cssEnd = startwhile (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) continueif (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-undefthemeColor.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-undefconsole.log("LC_THEME_CONFIG[i]", LC_THEME_CONFIG[i])// eslint-disable-next-line no-undefthemeColorConfig[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 || cssUrlcssUrlObj[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 = thisconst oldColorsObj = {}const newColorsObj = {}const cssUrlObj = {}console.log("wen()", win())// eslint-disable-next-line no-undefconsole.log("LC_THEME_CONFIG", LC_THEME_CONFIG)console.log("option", option)for (const i in option) {if (!themeColorConfig[i]) {// eslint-disable-next-line no-undefconsole.log("LC_THEME_CONFIG[i]", LC_THEME_CONFIG[i])// eslint-disable-next-line no-undefthemeColorConfig[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 || cssUrlcssUrlObj[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] + 1return 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 = newColorsresolve()} 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变量来实现这些逻辑!!
相关文章:

前端组件库自定义主题切换探索-03-webpack-theme-color-replacer webpack 同时替换多个颜色改造
接上一篇《前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-02》 这篇我们来开始改造,让这个插件最终能达到我们的目的: 首先修改plugin.config.js。 插件首先要在vue.config.js引用注册,因此先对…...

Redis高级-主从复制相关操作
2.1 主从复制简介 2.1.1 高可用 首先我们要理解互联网应用因为其独有的特性我们演化出的三高架构 高并发 应用要提供某一业务要能支持很多客户端同时访问的能力,我们称为并发,高并发意思就很明确了 高性能 性能带给我们最直观的感受就是:速…...

SPI总线设备驱动模型
SPI总线设备驱动模型 文章目录SPI总线设备驱动模型参考资料:一、平台总线设备驱动模型二、 数据结构2.1 SPI控制器数据结构2.2 SPI设备数据结构2.3 SPI设备驱动三、 SPI驱动框架3.1 SPI控制器驱动程序3.2 SPI设备驱动程序致谢参考资料: 内核头文件&…...

开发同事辞职,接手到垃圾代码怎么办?
小王新加入了一家公司,这家公司有点年头,所以连屎山都是发酵过的,味道很冲。和大多数时运不济的程序员一样,到了这种公司,做的大多数工作,就是修补这些祖传代码,为其添砖加瓦。每当被折腾的筋疲…...

gRPC简介
grpc简介 grpc介绍可以参考官网。无论是rpc还是grpc,可以这样理解,都知道过去使用的单单体架构,而在2011年5月威尼斯的一个软件架构会议上提出了微服务架构,围绕业务功能进行组织(organized around business capability)…...

《MySQL系列-InnoDB引擎25》表-InnoDB逻辑存储结构
InnoDB逻辑存储结构 从InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间(tablespace)。表空间又由段(segment)、区(extent)、页(page)组成。页在一些文档中有时也称为块(block),InnoDB存储引擎的逻辑存储结构…...

YOLOv8之C2f模块——与YOLOv5的C3模块对比
一、源码对比 YOLOv8完整工程代码下载:ultralytics/ultralytic C2f模块源码在ultralytics/nn/modules.py下,源码如下: class C2f(nn.Module):# CSP Bottleneck with 2 convolutionsdef __init__(self, c1, c2, n1, shortcutFalse, g1, e…...
动态规划实例——换零钱的方法数(C++详解版)
原写了 Java 版本的如何求解换钱的方法数,近期进行了一些细节上的补充,以及部分错误更正,将语言换为了 C 语言。 基础题目 假设你现在拥有不限量的 1 元、5 元、10 元面值纸币,路人甲希望找你换一些零钱,路人甲拿出的…...
linux c
射频驱动 管理硬件设备、分配系统资源 内核由中断服务程序 调度程序 内存管理程序 网络和进程间进程通信程序 linux支持动态加载内核模块 支持多处理smp机制 内核可以抢占preemptive linux系统拥有多个发行版,可能由一个组织 公司和个人发行 VGA兼容或者更…...
第十三章 系统错误消息 - 一般系统错误消息 S - Z
文章目录第十三章 系统错误消息 - 一般系统错误消息 S - Z第十三章 系统错误消息 - 一般系统错误消息 S - Z 错误代码描述<SUBSCRIPT>下标值不合法或Global引用过长。<SWIZZLE FAIL>打开了一个oref,然后试图在另一个无法引用的相关对象中进行搅拌。这可…...

移动web基础
初始缩小:布局视口大于视觉视口 初始放大:布局视口小于视觉视口 布局视口等于视觉视口(这种动作行为叫做理想视口) <meta name"viewport" content"width375" /> <meta name"viewport"…...
MyBatis和MyBatis_Plus有什么区别【面试常考题】
MyBatis和MyBatis_Plus的区别 MyBatis_Plus MyBatis_Plus 是一个 MyBatis 的增强工具,只是在 MyBatis 的基础上增强了却没有做改变,MyBatis-Plus支持所有MyBatis原生的特性,所有引入MyBatis-Plus不会对现有的MyBatis框架产生任何影响。 MyBa…...

华为OD机试用Python实现 -【统一限载货物数最小值】(2023-Q1 新题)
华为OD机试题 华为OD机试300题大纲统一限载货物数最小值题目描述输入描述输出描述说明示例一输入输出说明示例二输入输出说明Python 代码实现算法逻辑华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查…...
Vue入门小练习
文章目录Hello VueVue文本指令Vue属性绑定Vue双向绑定Vue事件绑定Vue猜数字Vue简单计算器Vue简单计算器升级版Vue循环遍历Vue员工列表练习Vue小练习Vue显示隐藏相关使用一些简单的小案例来熟悉Vue的基本使用方法 Hello Vue <!DOCTYPE html> <html lang"en"…...
Oracle-09-集合运算符篇
2022年4月13日23:01:25 通过本章学习,您将可以:1、描述 SET 操作符2、将多个查询用 SET 操作符连接组成一个新的查询目录 🏆一、SET OPERATORS ⭐️1.1、UNION /UNION ALL ⭐️1.2、INSTERSECT ⭐️1.3、MINUS dz...
获取浏览器(服务端)请求中特定的Cookie
有必要解释一下HttpServletRequest接口,因为我们需要从它里面获取Cookie。 HttpServletRequest HttpServletRequest是一个Java接口,提供了访问HTTP请求信息的方法,例如HTTP方法、请求URI、头部、参数和会话属性。它是Java Servlet API的一部…...

c++11 标准模板(STL)(std::unordered_set)(九)
定义于头文件 <unordered_set>template< class Key, class Hash std::hash<Key>, class KeyEqual std::equal_to<Key>, class Allocator std::allocator<Key> > class unordered_set;(1)(C11 起)namespace pmr { templat…...
python实战应用讲解-【实战应用篇】文件操作(附python示例代码)
目录 知识储备 使用 python-libarchive-c 模块 创建压缩文件 解压文件 查看信息...

OpenCV-Python系列(二)—— 图像处理(灰度图、二值化、边缘检测、高斯模糊、轮廓检测)
一、【灰度图、二值化】 import cv2 img cv2.imread("lz2.png") gray_img cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度图 # 二值化,(127,255)为阈值 retval,bit_img cv2.threshold(gray_img, 127, 255, cv2.THRESH_BINARY) cv2.imshow(photo1,im…...

ccc-台大林轩田机器学习基石-hw1
文章目录Question1-14Question15-PLAQuestion16-PLA平均迭代次数Question17-不同迭代系数的PLAQuestion18-Pocket_PLAQuestion19-PLA的错误率Question20-修改Pocket_PLA迭代次数Question1-14 对于有明确公式和定义的不需要使用到ml 智能系统在与环境的连续互动中学习最优行为策…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...