前端组件库自定义主题切换探索-02-webpack-theme-color-replacer webpack 的实现逻辑和原理-01

news2025/2/25 11:39:43

本文来研究写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

<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)">
              <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) {
      if (this.color !== color) {
        this.color = color
        updateTheme(color)
      }
    },
  }
}
</script>

然后我们将theme-example.vue配置成路由页面
theme.example.vue

<template>
  <basic-container>
    <div>
      <a-button type="primary">主色-primary</a-button>
      <a-button type="danger">警告色-danger</a-button>
    </div>
    <!--颜色设置组件-->
    <setting-drawer/>
  </basic-container>
</template>
<script lang="ts">
import BasicContainer from "../../components/layouts/basic-container.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>

basic-container.vue

<template>
  <pro-layout :menus="menus" :collapsed="collapsed" :handleCollapse="handleCollapse" :style="{ background: '#0B1C40' }" class="menu-slider">
    <!-- 页面内容-->
    <slot></slot>
  </pro-layout>
</template>

<script>

import ProLayout from "@ant-design-vue/pro-layout"
import exampleRoutes from "../../router/example-routes"

export default {
  name: "BasicContainer",
  components: {
    ProLayout
  },
  data () {
    return {
      menus: [], // 菜单
      collapsed: false, // 侧栏收起状态
    }
  },
  created() {
    this.menus = this.handleMenus([...exampleRoutes])
  },
  methods: {
    /**
     * 处理菜单数据
     */
    handleMenus(routes) {
      const menuRoutes = JSON.parse(JSON.stringify(routes))
      const menus = []
      for (let i = 0; i < menuRoutes.length; i++) {
        delete menuRoutes[i].component
        const { meta, children } = menuRoutes[i]
        const newMenus = { ...menuRoutes[i] }
        if (meta && meta.menu) {
          if (children && children.length) {
            newMenus.children = this.handleMenus(children)
          }
          menus.push(newMenus)
        }
      }
      return menus
    },
    /**
     * 窗口尺寸搜索展开
     * @param val
     */
    handleCollapse (val) {
      this.collapsed = val
    }
  }
}
</script>


菜单数据仅供参考,可自行处理。然后先看下效果

切换主题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》 中来进行吧

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

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

相关文章

Linux ALSA 之五 ALSA Proc Info

ALSA Proc Info一、概述二、Proc Files of Alsa Driver1、/proc/asound/xxx 简述2、创建 /proc/asound 目录树2.1 /proc/asound/version 文件2.2 /proc/asound/devices 文件2.3 /proc/asound/cards 文件2.4 /proc/asound/cardx 目录2.5 /proc/asound/pcm 文件一、概述 Linux系…

以“辛”为鉴,直播电商如何“知兴替”?

直播电商行业近年来创造了一个又一个销售神话。辛选2022年双11期间&#xff0c;18场直播销售额破亿。罗永浩在淘宝的首秀&#xff0c;6小时带货2亿元。直播间内外似乎一片繁华&#xff0c;但与此同时&#xff0c;也有许多曾经创造了带货奇迹的主播被市场淘汰&#xff0c;淡出视…

【笔记:第2课】学习开发一个RISC-V上的操作系统 - 汪辰 - 2021春

文章目录前言来源正文小结前言 创作开始时间&#xff1a;2023年1月9日20:11:06 如题&#xff0c;学习一下RISC-V。 来源 https://www.bilibili.com/video/BV1Q5411w7z5?p2&vd_source73a25632b4f745be6bbcfe3c82bb7ec0 正文 计算机硬件组成&#xff1a; 总线CPU&…

迷宫问题 | 深度优先

目录 一、说明 二、步骤 三、代码 四、结果 一、说明 什么是深度优先&#xff1f; DFS即Depth First Search&#xff0c;深度优先搜索属于图算法的一种&#xff0c;是一个针对图和树的遍历算法&#xff0c;利用深度优先搜索算法可以产生目标图的相应拓扑排序表&#xff0c…

身兼数据科学家和自由职业者,算算我在2022赚了多少钱?

2022年,我作为自由职业者数据科学家赚了多少钱&#xff1f;长按关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩扫码关注《Python学研大本营》&#xff0c;加入读者群&#xff0c;分享更多精彩大家好&#xff0c;首先&#xff0c;我已经等了很久了。2…

保姆级 | 最新Burpsuite安装配置

文章目录 0x00 前言 0x01 环境说明 0x02 准备工作 0x03 安装JDK 0x04 配置JDK环境 0x05 Burpsuite安装 0x06 Burpsuite环境配置 0x07 Burpsuite设置代理 0x08 Burpsuite使用验证 0x09 总结 0x00 前言 Burp Suite 是用于攻击 web 应用程序的集成平台&#xff0c;包含了…

mongodb 中做 join 的方法

【问题】Imagine you have a collection for posts, and each of these posts has the attribute userid: ObjectId( ), where ObjectID is referencing a document in the Users collection.How would you go about retrieving the user information (in this case, the user …

GC耗时高,原因竟是服务流量小?

简介 最近&#xff0c;我们系统配置了GC耗时的监控&#xff0c;但配置上之后&#xff0c;系统会偶尔出现GC耗时大于1s的报警&#xff0c;排查花了一些力气&#xff0c;故在这里分享下。 发现问题 我们系统分多个环境部署&#xff0c;出现GC长耗时的是俄罗斯环境&#xff0c;…

高校舆情监控系统建设(TOOM)如何做好教育行业舆情监控方案?

高校作为高密度学生聚集地&#xff0c;舆情管理上&#xff0c;需要保持高度的警惕性。高校中大学生是活跃在互联网上的重要群体&#xff0c;他们作为文化水平较高、思维较活跃的特殊群体&#xff0c;其网络中的言论合集往往会引发社会关注。高校舆情监控系统建设(TOOM)如何做好…

Sapped of vitality 生机已被耗尽 | 经济学人社论高质量双语精翻

选自TE20221217&#xff0c;leaders The global economy&#xff1a;Sapped of vitality 世界经济&#xff1a;生机已被耗尽 Why are the rich world’s politicians giving up on economic growth? 为什么发达国家的政客们不再追求经济增长目标&#xff1f; The prospect of …

智能音箱app开发-广州app开发定制

科技高速发展&#xff0c;智能产品遍地都是。日常生活都是智能化时代&#xff0c;智能音箱app也开始被开发出来。为用户提供便捷的服务&#xff0c;优化体验。 智能音箱app开发特点 一&#xff1a;搭建快速 线上渠道在各个行业中不可或缺的&#xff0c;因为线上平台不管是流量…

nginx学习笔记3(小d课堂)

nginx访问日志的作用&#xff1a; 我们先去查看一下我们的nginx.conf文件&#xff1a; BAT大厂应用运维平台案例统计 awk 默认以空格进行分隔。 {print $1} 只取第一个属性 sort -n排序 sort -rn倒序 uniq -c去重 head -n 100取前100个。 自定义日志统计接口性能 我们修改完…

上半年要写的博客文章28

上半年要写的博客文章21 这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个…

基于ESP32的蓝牙刷屏器自动点击器的制作

ESP32模块的选型&#xff1a; 这里是利用蓝牙连接手机来做点击器或刷屏器&#xff0c;ESP8266只有WIFI而ESP32有WIFI和蓝牙&#xff0c;所以选择ESP32模块。 ESP32模块可以选择ESP32-NodeMCU: 或ESP32-MiniKit: 这里使用的是ESP32 MINI KIT&#xff0c;Arduino环境下烧录选择如…

Nginx搭建Web服务器

环境&#xff1a; CentOS 7.2.1511 一、搭建静态web服务器 访问基本的静态页面&#xff08;基于IP访问&#xff09; 1.使用源码包编译安装nginx 启动ngin 直接在服务器测试访问&#xff1a; 访问nginx Web页面实现用户认证 修改nginx配置文件 2.生成密码文件&#xff…

64. 方法的值的传递及调用类内部的属性、方法

64. 方法的值的传递及调用类内部的属性、方法 文章目录64. 方法的值的传递及调用类内部的属性、方法1. 知识回顾2. 值的传递3. 没有值4. 需要向方法传递值5. 调用类内部的属性和方法6. 综合代码7. 类在爬虫中的应用7.1 创建类7.2 创建对象7.3 调用方法7.4 第2次调用方法7.5 第2…

MSE ZooKeeper 数据导入导出功能上线

作者&#xff1a;草谷 背景 MSE 提供了托管版的 ZooKeeper&#xff0c;拥有比自建开源 ZooKeeper 稳定性更高的SLA&#xff0c;同时管控面提供了丰富的服务自治功能。赶在2022年的岁末&#xff0c;MSE ZooKeeper 上线了一个非常实用的功能-数据导入导出功能&#xff0c;彻底解…

Mybatis自动生成增删改查代码

GitHub项目地址 Gitee项目地址 使用 mybatis generator 自动生成代码&#xff0c;实现数据库的增删改查。 1 配置Mybatis插件 在pom文件添加依赖&#xff1a; <plugins> <plugin><groupId>org.mybatis.generator</groupId><artifactId>myba…

宝塔面板Nginx开启Brotli压缩,提升网站加载速度

前言Google 认为互联网用户的时间是宝贵的&#xff0c;他们的时间不应该消耗在漫长的网页加载中&#xff0c;因此在 2015 年 9 月 Google 推出了无损压缩算法 Brotli。Brotli 通过变种的 LZ77 算法、Huffman 编码以及二阶文本建模等方式进行数据压缩&#xff0c;与 Gzip相比效率…

图解cross attention

英文参考链接&#xff1a; https://vaclavkosar.com/ml/cross-attention-in-transformer-architecture 交叉注意力与自我注意力 除了输入&#xff0c;cross-attention 计算与self-attention相同。交叉注意力不对称地组合了两个相同维度的独立嵌入序列&#xff0c;相比之下&a…