前端组件库自定义主题切换探索-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 智能系统在与环境的连续互动中学习最优行为策…...
hadoop03-MapReduce【尚硅谷】
大数据学习笔记 MapReduce 一、MapReduce概述 MapReduce是一个分布式运算程序的编程框架,是基于Hadoop的数据分析计算的核心框架。 MapReduce处理过程为两个阶段:Map和Reduce。 Map负责把一个任务分解成多个任务;Reduce负责把分解后多任务处…...
测牛学堂:软件测试python学习之异常处理
python的捕获异常 程序在运行时,如果python解释器遇到一个错误,则会停止程序的执行,并且提示一些错误信息,这就是异常。 程序停止执行并且提示错误信息,称之为抛出异常。 因为程序遇到错误会停止执行,有时…...
图神经网络--图神经网络
图神经网络 图神经网络图神经网络一、PageRank简介1.1互联网的图表示1.2PageRank算法概述1.3求解PageRank二、代码实战2.1引入库2.2加载数据,并构建图2.3计算每个节点PageRank重要度2.4用节点尺寸可视化PageRank值一、PageRank简介 PageRank是Google最早的搜索引擎…...
React useCallback如何使其性能最大化?
前言 React中最让人畅谈的就是其带来的灵活性,可以说写起来非常的舒服。但是也就是它的灵活性太强,往往让我们忽略了很多细节的地方,而就是这些细节的东西能进行优化,减小我们的性能开销。可以说刚学React和工作几年后写React的代…...
长尾关键词使用方法,通过什么方式挖掘长尾关键词?
当你在搜索引擎的搜索栏中输入有关如何使用长尾关键词的查询时,你可能希望有简单快捷的方式出现在搜索结果中,可以帮助你更好地应用seo。 不过,这里要记住一件事:SEO 策略只会为你的网站带来流量;在你的产品良好之前&a…...
【网络编程套接字(一)】
网络编程套接字(一)理解源IP地址和目的IP地址理解源MAC地址和目的MAC地址理解源端口号和目的端口号PORT VS PID认识TCP协议和UDP协议网络字节序socket编程接口socket常见APIsockaddr结构简单的UDP网络程序服务端创建套接字服务端绑定字符串IP VS 整数IP客…...
shell脚本入门
实习的时候第一个月的考核就是如何部署一个云资源,当时走的捷径(杠杠的搜索能力hhhh)找到了一个shell脚本一键部署,后来被leader问起来就如实说了,leader问有没有看懂shell脚本中的逻辑……(没有࿰…...
【经典蓝牙】 蓝牙HFP层协议分析
HFP 概述 HFP概念介绍 HFP(Hands-Free Profile), 是蓝牙免提协议, 可以让蓝牙设备对对端蓝牙设备的通话进行控制,例如蓝牙耳机控制手机通话的接听、 挂断、 拒接、 语音拨号等。HFP中蓝牙两端的数据交互是通过定义好的AT指令来通讯的。 &am…...
互联网摸鱼日报(2023-02-26)
互联网摸鱼日报(2023-02-26) InfoQ 热门话题 迁移工具 Air2phin 宣布开源,2 步迁移 Airflow 至 Dolphinscheduler 专访奇安信董国伟博士:目前开源安全的现状并不乐观,但其重要性已成各方共识 专访Brian Behlendorf&…...
关于程序员中年危机的一个真实案例
关于中年危机,网上已经有了各种各样的解读。但是,这两天一个学员跟我简单几句聊天,却触发了对于中年危机的另一种思考。如果你曾经也有点迷茫,或许你可以稍微花几分钟看下这个故事。 一、无奈的故事 39岁还出来面试&#x…...
wordpress菜单手机显示下拉/北京网站seo哪家公司好
题目来源:链接 题目描述: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 示例 1: 输入:matrix [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5] 代码实现: def s…...
建设一个企业网站到底要多少钱/广州今日新闻头条新闻
渐变边框可以使用border-image,但带有圆角的渐变边框单靠border-image却无法实现,那有哪些方法可以实现圆角渐变边框呢?css实用小知识,你肯定用得上 方案一:使用border-imageclip-path实现 <style> .radius-gra…...
wordpress评论函数/百度新闻首页新闻全文
由于新浪SAE对文件权限的限制,cache目录无法修改权限,因此原版Codeigniter无法直接使用。可以尝试codeIgniter 2.10 for SAE:http://code.google.com/p/ci-sae/。在database.php中配置如下:$db[default][hostname] SAE_MYSQL_HOS…...
游戏推广是干什么/谷歌seo
防火墙原是指建筑物大厦用来防止火灾蔓延的隔断墙。从理论上讲,Internet防火墙服务也属于类似的用来防止外界侵入的。它可以防止Internet上的各种危险(病毒、资源盗用等)传播到你的网络内部。而事实上,防火墙并不像现实生活中的防…...
凡科网做网站怎么样/英文外链平台
VB.NET中对于表格数据的显示经常使用到DataGridView控件,其以丰富多样的数据表呈现形式被程序猿喜爱。本人在做一个小系统中运用DataGridView控件的部分属性,这些功能的使用在使用之初比較不易去理清,随着系统接近尾声,如今对一些…...