高度兼容低版本的 antd 的动态主题方案

4,047 阅读3分钟

前言

定制主题对于每一个前端开发来说,简直就是家常便饭,几乎每一个项目都会用到它。目前比较流行的方式是静态主题方案,简单来说就是在代码编译阶段,将主题配置转换成特定主体的 css 代码,这里要强调的是代码编译阶段,也就意味着在代码构建完成之后,主题是无法动态调整的。

动态主题的需求在前端社区中也一直是呼声比较高的,社区也也涌现了一些解决方案。antd-theme-generator是我最早接触的动态主题方案,但是美中不足的是样式调制能力比 antd 的定制主题的效果差很多,本文章后面也会详细介绍下这块。后来 antd3 提出了基于 css variables 的方案的动态主题方案,目前还是实验性质的,相信不久的将来会正式发布的。antd3 的动态主题方案也不是完美的,缺点就是必须升级到 antd3 版本,要移除 babel-plugin-import插件,ant-prefix变量变更之后的兼容很麻烦。

但是在实际案例场景中,产品往往会提这些诉求的,作为前端开发的你往往只能委婉的拒绝,因为你也知道目前的老项目要支持这些有太多的不确定因素。但是作为一名前端开发者怎么能轻易说不行呢?本文就给大家带来基于 antd-theme-generator 的高度兼容低版本的 antd 动态主题方案。

antd-theme-generator官方方案

不了解antd-theme-generator插件,可以跳转 git:antd-theme-genrator 官网快速浏览下,接下来将从原理角度介绍下这个款插件。

antd-theme-generator插件的核心是:

  • 1、通过js脚本提取所有 antd 的 less, 生成支持特定变量控制的 less 样式文件。
// theme.js
const { generateTheme } = require('antd-theme-generator');

const options = {
  antDir: path.join(__dirname, './node_modules/antd'),
  stylesDir: path.join(__dirname, './src'), // all files with .less extension will be processed
  varFile: path.join(__dirname, './src/styles/variables.less'), // default path is Ant Design default.less file
  themeVariables: ['@primary-color'],
  outputFilePath: path.join(__dirname, './public/color.less') // if provided, file will be created with generated less/styles
  customColorRegexArray: [/^fade(.*)$/], // An array of regex codes to match your custom color variable values so that code can identify that it's a valid color. Make sure your regex does not adds false positives.
}

generateTheme(options).then(less => {
  console.log('Theme generated successfully');
})
.catch(error => {
  console.log('Error', error);
})

执行生成命令。

// package.js script:
"theme": "node script/theme.js"

最终得到下面截图的 less 文件。 image.png 完整例子参考:color.less文件 显而易见,antd-generator-theme是基于 less 的,并不是和 antd3 的 css variables 方案。

  • 2、接下来在HTML文件加入下面的配置。
<link rel="stylesheet/less" type="text/css" href="/color.less" />
<script>
  window.less = {
    async: true,
    env: 'production'
  };
</script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/less.js/2.7.2/less.min.js"></script>
  • 3、然后你在运行时代码中执行下面的命令即可任意调整主题色。
window.less.modifyVars({
  '@primary-color': '#0035ff'
})

到这里为止,你会觉得上面的额方案不是很完美吗!不需要任何调整。到底是不是这样呢?我们慢慢分解其中的奥秘。

antd-theme-generator真的是完美方案吗?

首先回到第一步生成命令中,你会发现下面的配置。

  themeVariables: ['@primary-color'],

结合生成的 less 样式结果来看,只有写入到themeVariables数组中的变量,才会被导出 less 的变量,以官方的例子来看,最终只会生成@primary-color: #F0052d9;这一个变量,也就是说你在运行时也就只能调整这一个变量的值。假如你需要调整 @btn-primary-bg 的值,很抱歉不行,你需要在themeVariables变量组中增加该变量,并且重新生成 less 样式文件,holyshit!

你可能会想那我把所有的变量都写进去不就可以吗?思路上是可以的,本文这里将通过 less-vars-to-js提取出来的变量提取后,最终的配置我放在<dynamicTheme 仓库 - color.js>中,请大家自行取用。

到这里你迫不及待的再次生成 less 样式文件,然后项目跑起来,并且修改了@primary-color的值,然后打开页面看了下,holyshit!

你发现依赖于@primary-color@link-color并没发生变化,仔细看下你的 less 样式文件。

image.png 你发现变量之间的依赖关系丢失了,导致antd-theme-generator的定制效果和官方的定制效果存在较大差异。

最终方案

目前我们遇到的难点-样式变量的依赖关系丢失,导致样式变量的控制效果和 antd 主题定制的效果不一致。

首先我们从 less 文件中提取依赖关系,这里直接上代码。

const lessToJs = require('less-vars-to-js');
const fs = require('fs');
const path = require('path');

const paletteLess = fs.readFileSync(path.resolve(__dirname, './styles/themes/default.less'), 'utf8');

// Pass in file contents
const palette = lessToJs(paletteLess, { resolveVariables: false, stripPrefix: false });
console.log(palette, 'palette');
module.exports = palette;

通过less-vars-to-js库提取 antd 的全部样式变量,详细代码在【DynamicTheme/script/antdVariableJSON.js文件】。

然后分析依赖关系,生成各个变量之间的依赖关系。

假如你需要你要修改@primary-color,实际上你不仅仅要更新该变量,所有依赖该变量的从属样式变量也要更新。

企业微信截图_b98ca59c-ecfd-4442-beea-4fabb1e4048d.png

回到上文提到的例子,如果同时修改 @primary-color@link-color,那么理论上直接使用@link-color 的样式值要能够覆盖@primary-color,使用@link-color为最终值,那么这里要按照依赖关系进行变量依赖优先级进行合并。

  beforeMount() {
    const { themesVar } = this.privateConfig.themeConfig.brandSetting;
    const themes = {};
    Object.keys(themesVar).forEach((key) => {
      //驼峰转@连线格式
      themes[`@${this.change(key)}`] = themesVar[key];
    });
    window.less
      .modifyVars(formatThemeConfig(themes))
      .then(() => {
        console.log('', themes);
      })
      .catch((error) => {
        console.log(error);
      });
  },

formatThemeConfig的细节就不做过多描述,直接上《代码链接》。

结语

本方案是对低版本高度兼容的动态主题方案,并不是最优的方案,但是最实用的方案。为什么这么说?

首先antd-theme-generator是基于 less 来实现的,并不是主流的 css variables 方案,未来等 antd 试验性质的动态主题方案成熟之后,必然会抛弃它。

其次这是本方案的性能相对css variables直接使用浏览器底层相比,明显是不足的。

因此其不是最优的方案。

但是目前 css variables 的方案,最终生成的 css 文件如下:

image.png image.png 很明显和常规的 less 的规则有很大不同,我预期其切换到 css variables 的改造成本不会低,甚至官方未必支持无感知兼容。

因此在新版 antd 动态主题方案,从试验性质成熟到完善设计,并提供很好的迁移方案之前,本方案可以帮助你切实解决动态主题方案的在兼容性方面的痛点,是最实用的方案。