本文来研究写webpack-theme-color-replacer webpack 的实现逻辑和原理。
上一篇我们讲过, webpack-theme-color-replacer webpack 基本思路就是,webpack构建时,在emit事件(准备写入dist结果文件时)中,将即将生成的所有css文件的内容中 带有指定颜色的css规则单独提取出来,再合并为一个theme-colors.css输出文件。然后在切换主题色时,下载这个文件,并替换为需要的颜色,应用到页面上,但是具体的细节确并不清楚,我们想要看看是否可以改造达到自己的需求和期望,就得具体看下里面的实现过程逻辑
1、注册插件
首先,我们还是在项目根目录下建config文件夹,里面有plugin.config.js文件
同样,要在vue.config.js注册插件
以上两点代码参考第一篇:前端组件库自定义主题切换探索-01
改造用于测试组件
为了方便研究,我们将ant-design-pro的 setting-draw组件挪过来,并做下改造只保留主题设置功能,目录结构如下:
这里测试代码,是vue2+typescript+javascript混写(项目是typescript+vue2搭建,但是移植的代码是javascript),搭建可参考:Vue2+typescript写法总结
index.ts
import SettingDrawer from "./SettingDrawer.vue"
export default SettingDrawer
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 = newPrimaryColor => {
themeColor.changeColor(newPrimaryColor).finally(() => {
setTimeout(() => {
}, 10)
})
}
export { updateTheme, colorList }
themeColor.js
import client from "webpack-theme-color-replacer/client"
import generate from "@ant-design/colors/lib/generate"
export default {
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)
},
changeColor (newColor) {
var options = {
newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors`
changeUrl (cssUrl) {
return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path
}
}
return client.changer.changeColor(options, Promise)
}
}
settingDraw.vue
切换颜色列表
{{ item.key }}
然后我们将theme-example.vue配置成路由页面
theme.example.vue
主色-primary
警告色-danger
basic-container.vue
菜单数据仅供参考,可自行处理。然后先看下效果
[video(video-b6ofzHj3-1673234646284)(type-csdn)(url-https://live.csdn.net/v/embed/268814)(image-https://video-community.csdnimg.cn/vod-84deb4/e876f8f08fbf71ed80040764a0fd0102/snapshots/dff5edb95111439c846d5582efabd016-00001.jpg?auth_key=4826828998-0-0-1653dcf315a7e0650ef9d205253f2049)(title-切换主题setting-drawer)]
2、插件相关调用栈
可以正常切换主题色,然后我们来看下调用栈
从上图可以看出,最终由replaceCssText完成样式替换,而cetCssTo调用了replacCssText,我们先看下这两个函数代码
可以看到这两个函数仅做赋值和替换工作,无其他逻辑
然后我们来看下getCssString
这里有个判断逻辑,就是是否将css嵌入js,那到底走哪个,我们来操作看下即可。如果没有嵌入,肯定会发起请求。然后因为getCssString在第一次操作才会调用,所以我们要先清空页面(刷新),然后操作看看浏览器的网络请求
3、颜色提取原理
可以看到,确实发起了请求,并且名字是theme-colors-8addcf28.css
上图是请求的css文件内容,搜索ant-btn-primary我们发现该内容包含了ant-btn-primary及ant-btn-primary相关的比如hover样式内容,当然还有其他色号是1890ff的内容,以及其他颜色如40a9ff
然后我们尝试搜索ant-btn-danger,却查不到任何结果。当然如果我们将plugin.config中的getAntdSerials函数调用参数改为#F5222D,重启项目后,再测试,这时候搜索结果就会发现,ant-btn-primary差不到任何内容,ant-btn-danger就可以查到
这里说明了一点,webpack-theme-color-replacer确实是通过我们在调用getAntdSerials时传递的颜色参数来提取颜色数据的,我们看下getAantdSerials的返回结果,加上3个打印
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
}
重启后看下运行控制台,注意这里看的时运行控制台,不是浏览器控制台,因为这段代码在项目启动时vue.config.js里面就调用了
结合刚才看到的css返回结果里面有其他颜色的情况,我们不妨对比查找一下,果然像40a9ff,e6f7ff等颜色,两边都存在。也就是插件内部通过matchColors的颜色结果去提取颜色样式
matchColors包含两部分,一部分是webpack-theme-color-replacer的计算颜色,另一部分是ant-design-vue的计算颜色,其中ant-design-vue的计算颜色和ant-design-vue的颜色设计体系相关,比如hover颜色,active颜色。webpack-theme-color-replacer的计算颜色的依据暂不清楚,不过我们可以看到,他们都是根据我们提供的颜色的深浅变化颜色
另外,在setCssText里面,将css代码注入到body里面,便于读取,我们点开页面html,可以看到
这里的css内容,和theme-colors-8addcf28.css文件的内容一致
4、思路问题重新整理
到这里,我们暂时先对之前的分析做下整理
a、我们注册插件时,插件通过我们提供的颜色 (getAntdSerials("#1890ff")) 去做样式筛选
b、筛选后的颜色,存放在一个css文件中
c、在第一次替换时,请求提取出来的css文件内容
d、将请求到的css内容提取出来放在页面的style标签里面
e、读取style标签的css内容,根据正则匹配替换后,重新赋值回去,完成颜色替换
想要达到我们的目标,比如可以分别对primary和danger的颜色进行替换,就要弄清楚以下几点
a、theme-colors-8addcf28.css 的内容是在哪里生成的?
我们现在知道是根据我们提供的颜色筛选出来的,但是在哪筛选?还不知道,之前看过的文件里面没有找到筛选的具体代码,一上来就是直接请求theme-colors-8addcf28.css的内容
b、theme-colors-8addcf28.css 文件名是如何定义的?
当前插件只支持一种颜色及其变化颜色的替换,并且theme-colors-8addcf28.css里面只有一种颜色(包括变化颜色),想要分别支持多种,怕是要有多个文件才行
5、theme-colors-8addcf28.css url 来源查找
既然如此,我们就先根据请求theme-colors-8addcf28.css文件的url参数进行追踪,结合之前的调用栈分析代码,我们很快就找到了目标代码
这里首先theme_COLOR_config是文件内的变量,它是由win()[WP_THEME_CONFIG]赋值而来
第二,WP_THEME_CONFIG 是一个全局的变量,也就是window.WP_THEME_CONFIG,当前文件没有,我们得去其他地方查找
第三,cssUrl有两个来源,theme_COLOR_config.url 或者 options.cssUrl,至于是哪个,我们打印确认一下
添加打印代码后,我们操作一下,看下浏览器控制台
显然,url和 WP_THEME_CONFIG 有关
**查找WP_THEME_CONFIG **
当前文件没有 WP_THEME_CONFIG 的定义 ,那我们只能去其他地方查找,首先我们看下vue.config.js,这里面显然没有,plugin.config.js也没有。这两处项目主题插件注册相关的文件没有,那就只能去插件内部找找看了。
上面是插件的文件结构,themeColorChanger.js我们已经看过,formElementUI不用看,这个看名字就知道是专门给element-ui写的插件,其他文件,我们就逐个翻一遍吧。最终我们在src下的index.js里面找到这个变量的定义
这是注册webpack插件的时候挂载进去的,由JSON.stringify(this.handler.options.configVar)赋值而来,接下来我们对this.handler.options.configVar进行追踪
configVar追踪
然后我们很快就在Handler.js里面找到了相关代码
第一我们看到了configVar的定义
第二,我们看到了和theme-colors-8addcf28.css很像的fileName
回到themeColorChange.js,theme_COLOR_config 是通过调用win函数,然后取WP_THEME_CONFIG 变量属性得来,我们不妨先看下win函数调用得结果
win执行的结果就是window对象,点开后,我们在一大堆属性里面,找到tc_cfg_7781740664726529,即configVar
之所以找tc_cfg_7781740664726529,是根据configVar: 'tc_cfg_' + Math.random().toString().slice(2)、WP_THEME_CONFIG: JSON.stringify(this.handler.options.configVar)和win()[WP_THEME_CONFIG]几行代码推断而来,查看结果后也证明了我们的猜测,configVar是挂载到window下的属性键名,而fileName则是属性里面的url
下面我们将css改为css2,tc_cfg_改为tc_cfg_test_
重启项目测试一下
确实已经被更改
然后我们在Handler.js的this.options下面看到这行代码,this.assetsExtractor = new AssetsExtractor(this.options),也就是optins的配置是在AssetsExtractor类中处理的
由于篇幅太长,我们接下来的进一步追踪在下一篇:《前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-02》 中来进行吧