Exploring custom theme switching in the front-end component library-02-webpack-theme-color-replacer Implementation logic and principles of webpack-01

This article will study the implementation logic and principles of writing webpack-theme-color-replacer webpack.
As we mentioned in the previous article, the basic idea of ​​webpack-theme-color-replacer webpack is that when webpack is built, in the emit event (when preparing to write the dist result file), the contents of all css files to be generated will be specified. The color css rules are extracted separately and then merged into a theme-colors.css output file. Then when switching the theme color, download this file, replace it with the required color, and apply it to the page . However, the specific details are not clear. If we want to see if we can transform it to meet our own needs and expectations, we have to be specific. Take a look at the implementation process logic inside

1. Register plug-in

First, we still create the config folder in the project root directory, which contains the plugin.config.js file.
Similarly, we need to register the plugin in vue.config.js

Refer to the first article for the above two points of code: [Exploration of custom theme switching in front-end component library-01]

Retrofitting components for testing

In order to facilitate research, we moved the setting-draw component of ant-design-pro and made a modification to retain only the theme setting function. The directory structure is as follows:

Insert image description here

image.png

The test code here is a mixture of vue2+typescript+javascript (the project is built with typescript+vue2, but the transplanted code is javascript). For construction, please refer to: Summary of Vue2+typescript writing method

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>

Then we configure theme-example.vue as the routing page
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>

The menu data is for reference only and can be processed by yourself. Then take a look at the effect first

[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. Plug-in related call stack

You can switch the theme color normally, and then let's take a look at the call stack

Insert image description here
Insert image description here

image.png

As can be seen from the above figure, replaceCssText finally completes the style replacement, and cetCssTo calls replacCssText. Let’s take a look at the codes of these two functions first.

Insert image description here

image.png

You can see that these two functions only do assignment and replacement work, no other logic.
Then let's look at getCssString.

Insert image description here

image.png

There is a judgment logic here, that is, whether to embed css into js, ​​and which one to go for. Let’s take a look. If there is no embedding, the request will definitely be initiated. Then because getCssString will only be called in the first operation, we need to clear the page (refresh) first, and then operate to see the browser's network request.

Insert image description here

image.png

3. Color extraction principle

As you can see, the request was indeed initiated, and the name is theme-colors-8addcf28.css
Insert image description here

image.png

The picture above is the content of the requested css file. Searching for ant-btn-primary, we found that the content includes ant-btn-primary and ant-btn-primary-related content such as hover style content. Of course, there are also other content with the color number 1890ff. and other colors such as 40a9ff

Then we tried searching for ant-btn-danger, but couldn't find any results. Of course, if we change the getAntdSerials function call parameter in plugin.config to #F5222D, restart the project, and then test again, then the search results will find that ant-btn-primary is not different, and ant-btn-danger is Can be found

It is explained here that webpack-theme-color-replacer does extract color data through the color parameters we pass when calling getAntdSerials. Let’s take a look at the return result of getAantdSerials, plus 3 prints.

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
}

After restarting, take a look at the running console. Note that what you look at here is the running console, not the browser console, because this code is called in vue.config.js when the project starts.

Insert image description here

image.png

Combined with the fact that there are other colors in the CSS return results we just saw, we might as well compare and search. Sure enough, colors such as 40a9ff, e6f7ff, etc. exist on both sides. That is, the plug-in uses the color results of matchColors to extract the color style.

matchColors consists of two parts. One part is the calculated color of webpack-theme-color-replacer, and the other part is the calculated color of ant-design-vue. The calculated color of ant-design-vue is related to the color design system of ant-design-vue. , such as hover color and active color. The basis for calculating the color of webpack-theme-color-replacer is not yet clear, but we can see that they all change colors based on the depth of the color we provide.

Insert image description here

image.png

In addition, in setCssText, inject the css code into the body for easy reading. We click on the page html and you can see

Insert image description here

image.png

The css content here is consistent with the content of the theme-colors-8addcf28.css file.

4. Reorganize the thinking issues

At this point, let’s sort out the previous analysis for now.
a. When we register the plug-in, the plug-in uses the color we provide ( getAntdSerials(“#1890ff”) ) to do style filtering
. b. The filtered color is stored in a css In the file
c. During the first replacement, request the extracted css file content.
d. Extract the requested css content and put it in the style tag of the page.
e. Read the css content of the style tag and match and replace according to the regular rules. , reassign the value back to complete the color replacement

If we want to achieve our goal, for example, we can replace the colors of primary and danger respectively, we need to understand the following points:

a. Where is the content of theme-colors-8addcf28.css generated?
We now know that we filter based on the colors we provide, but where? I don’t know yet. I didn’t find the specific code for filtering in the files I read before. The first thing I did was to directly request the content of theme-colors-8addcf28.css.
b. How is the file name of theme-colors-8addcf28.css defined?
The current plug-in only supports the replacement of one color and its changing colors, and there is only one color (including changing colors) in theme-colors-8addcf28.css. If you want to support multiple colors respectively, you may need multiple files.

5. theme-colors-8addcf28.css url source search

In this case, we first trace according to the url parameters of the request theme-colors-8addcf28.css file. Combined with the previous call stack analysis code, we quickly found the target code.

Insert image description here

image.png

First, theme_COLOR_config is a variable in the file. It is assigned by win()[WP_THEME_CONFIG].
Second, WP_THEME_CONFIG is a global variable, which is window.WP_THEME_CONFIG. The current file does not have it, so we have to look elsewhere.
Third, cssUrl has two sources, theme_COLOR_config.url or options.cssUrl. As for which one it is, let’s print it to confirm.

Insert image description here

image.png

After adding the printing code, let’s do it and look at the browser console

Insert image description here

image.png

Obviously, the url is related to WP_THEME_CONFIG

**Search for WP_THEME_CONFIG **
The current file does not have a definition of WP_THEME_CONFIG, so we can only find it elsewhere. First, let's look at vue.config.js. It is obviously not there, and neither is plugin.config.js. There are no files related to the registration of the theme plug-in in these two projects, so you can only look inside the plug-in.

Insert image description here

image.png

The above is the file structure of the plug-in. We have already seen themeColorChanger.js. You don’t need to look at formElementUI. You can tell by the name that this plug-in is specially written for element-ui. Let’s go through the other files one by one. Finally we found the definition of this variable in index.js under src

Insert image description here

image.png

This is mounted when registering the webpack plug-in, and is assigned by JSON.stringify(this.handler.options.configVar). Next, we track this.handler.options.configVar.

configVar tracking
Insert image description here

image.png

Then we quickly found the relevant code in Handler.js.
First, we saw the definition of configVar.
Second, we saw the fileName which is very similar to theme-colors-8addcf28.css.

Back to themeColorChange.js, theme_COLOR_config is obtained by calling the win function and then taking the WP_THEME_CONFIG variable attribute. We might as well look at the results of the win function call first.

Insert image description here

image.png

The result of win execution is the window object. After clicking it, we find tc_cfg_7781740664726529, which is configVar, among a lot of attributes.

Insert image description here

image.png

The reason why I found tc_cfg_7781740664726529 is based on configVar: 'tc_cfg_' + Math.random().toString().slice(2), WP_THEME_CONFIG: JSON.stringify(this.handler.options.configVar) and win()[WP_THEME_CONFIG] It is inferred from the line of code. After viewing the results, our guess is also proved. configVar is the attribute key name mounted to the window, and fileName is the url in the attribute.

Next we change css to css2 and tc_cfg_ to tc_cfg_test_

Insert image description here

image.png

Restart the project and test it

Insert image description here

image.png
Insert image description here

image.png

has indeed been changed

Then we see this line of code under this.options of Handler.js, this.assetsExtractor = new AssetsExtractor(this.options), that is, the configuration of optins is handled in the AssetsExtractor class

Since the article is too long, we will follow it further in the next article: "Exploration of custom theme switching in front-end component library-02-webpack-theme-color-replacer Implementation logic and principle of webpack-02"

Guess you like

Origin blog.csdn.net/baidu_33164415/article/details/135259831