如何在 umi 系项目中实现动态换肤

简介: 如何在 umi 系项目中实现动态换肤

我所知道的动态换肤方案有两种,一种是通过使用 less.modifyVars 修改 less 变量实现,一种是使用 var css 实现,由于 var css 很多浏览器都不支持。而且我们项目的主要场景在于移动端,所以能选择的就只有通过 less 实现的方案了。


原理简述

原理其实很好理解,将包含 less 变量的 css 类提取出来,通过修改变量的值重新生成新的 css 类,再添加到 dom 中。如在项目代码中,编写样式如下:


@abcd-efg : #f3574c;
.center {
  color: @abcd-efg;
  font-size: 26px;
  height: 50px;
}


在框架中编译之后,会产生这样的 css ,(如 umi.css):

.center {
  color: #f3574c;
  font-size: 26px;
  height: 50px;
}


正常不需要动态换肤的场景下,这就是我们最终需要的 css 样式。如果需要动态换肤,那我们就可以保留下这些 less 变量,重新生成一个 less 文件(假设命名是 alita.less)。

.center {
  color: #f3574c;
  font-size: 26px;
  height: 50px;
}
@abcd-efg : #f3574c;
.center {
  color: @abcd-efg;
}


然后 less.js 会将这个 less 文件转化成 css 文件,放到 dom 上。

最终我们部署的 html 文件大致如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <link rel="stylesheet" href="/umi.css" />
</head>
<body>
  <link rel="stylesheet/less" type="text/css" href="/alita.less" />
  <script src="less.js"></script>
</body>
</html>


当用户访问页面时,在浏览器端,less.js 将 dom 中的 less 文件编译成 css 样式,html 变化大致如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <link rel="stylesheet" href="/umi.css" />
  <!-- 这里解析之后是
  .center {
    color: #f3574c;
    font-size: 26px;
    height: 50px;
  } -->
</head>
<body>
  <link rel="stylesheet/less" type="text/css" href="/alita.less" />
  <style type="text/css" id="less:alita">
  /* 这里就是上面的 less 文件编译而成 */
  .center {
    color: #f3574c;
  }
  </style>
  <script src="less.js"></script>
</body>
</html>


当我们在项目中使用 less.modifyVars 修改变量会触发 less 文件重新编译。

window.less.modifyVars({
  'abcd-efg': '#0000FF'
})
<style type="text/css" id="less:alita">
  /* 这里就是重新生成的 css 样式 */
  .center {
    color: #0000FF;
  }
  </style>

然后理解了原理,那么接下来就是实现了。



实现

变量提取

看了一些实现,还是觉得 antd 的官网的实现最为靠谱(Ant Design Runtime Theme Update #10007),并且 mzohaibqc 已经写了一个很好的工具 (antd-theme-generator),看了下源码,里面写死了 antd 的目录路径和变量名称,但是提供的方法基本上都可以使用,因为我需要的是移动端的方案,即需要的是 antd-mobile,好在 antd-mobile@2 的结构和 antd 基本上一致,通过简单修改之后,就可以使用。

但是发现一个问题,变量修改只能够修改 antd-mobile 组件中的变量,并无法修改项目中用到的变量名。


import { Button } from 'antd';
import styles from './index.less';
// index.less
// @import '~antd-mobile/lib/style/themes/default.less';
// .center {
//   .primary {
//     color: @brand-primary
//   }
// }
const Page: FC<PageProps> = () => {
  return (
    <div className={styles.center} >
      <Button type="primary" >按钮</Button>
      <span className={styles.primary}>这里的颜色,在less文件中使用了主题色的变量。@brand-primary </span>
    </div>
  );
}


修改变量之后发现按钮和其他用到主题色的组件都发生了变化,但是在项目中使用一样变量的类却无法改变,

window.less.modifyVars({
  'brand-primary': '#FF0000'
})

1.gif

初次猜测原因可能是自定义的 less 文件没有被编译到。通过查看最终的产物,发现 antd-theme-generator 编译的时候读取到的文件是原始文件,而 umi 项目中使用了 css module 之后,在 css-loader 的时候,类名会被默认加上后缀,导致 less 编译后的类名为 .center 而真实的类名为 .center__kjahd


因为 umi 生命周期中,并没有一个时机,能够获取到带有 less 变量和类名后缀的文件。因此直接从 webpack 构建环节中,读取了 umi 编译后的 css 文件。考虑到可能存在按需加载的情况,因此取了所有的 css 文件。


class UmiThemePlugin {
  apply(compiler) {
    const options = this.options;
    compiler.hooks.emit.tapAsync('UmiThemePlugin', (compilation, callback) => {
      options.customCss = '';
      Object.keys(compilation.assets).map((i) => {
        if (i.endsWith(".css")) {
          options.customCss = `${options.customCss}\n${compilation.assets[i].source()}`
        }
      })
      generateTheme(options)
    });
  }
}
module.exports = UmiThemePlugin;


既然 less 转 css 都通过 umi 编译了,那 antd-theme-generator 中就没有必要二次编译了。因此简单的删除了这里面编译 css 文件的内容。



umi 插件

本着框架中做的越多,项目交付中做的就越少的原则,将 antd-theme-generator 文档中要求的,手动引入的文件,和其他需要注意的事项通过 umi 插件的形式实现。最终完成 @alitajs/plugin-theme,安装完成后,在配置文件中配置使用:

plugins:['@alitajs/plugin-theme'],
  dynamicTheme:{
    type:'antd-mobile',
    varFile: path.join(__dirname, '../src/default.less'),
    themeVariables: ['@brand-primary','@abcd-efg'],
  }


属性 说明
type 声明是 antd 还是 antd-mobile ,会自动找到包的路径
varFile 声明 less 变量的文件路径,未提供的话,会默认找到 'style/themes/default.less'
themeVariables 需要提取的变量名,需要显示指明,才能在修改变量时使用,因为需要修改的变量越多,生成的 less 文件越大

遗留的问题

  1. less 版本问题
    引入less@2.7 的用 window.less.modifyVars 的方式可以。但是在项目中使用了less@3 ,使用 import less from less;less.modifyVars 的方式,就算 javascriptEnabled 设置为 true ,也是不能使用.bezierEasingMixin();
    相关 Issues  https://github.com/mzohaibqc/antd-theme-generator/issues/41#issuecomment-768734824
  2. less 变量的值必须唯一
    由于使用的 css 是由 umi 编译后的文件,中间未记录 less 变量,后续动作是采用值匹配来做反向绑定的。
    缺点就是如果两个变量名都指明了同一个颜色值,最终会被合并为一个。
    好处是就算在项目中写色值的时候忘记使用变量,也可以实现动态换肤,这对于遗留项目的功能跟进有着极大的好处。



适用的项目

理论上所有 umi 系的项目都可以使用,比如 umi、dumi、ant-design-pro、alita 等。目前测试了 umi 、alita 和 ant-design-pro 的项目。


闭眼测试 ant-design-pro

  1. 拉取当前 v5 分支代码
  2. yarn add @alitajs/plugin-theme
  3. config/config.ts 中添加配置
  4. src/pages/User/login/index.tsx 中,随便写了一个按钮


config/config.ts

export default defineConfig({
+  plugins: ['@alitajs/plugin-theme'],
+  dynamicTheme: {
+    type: 'antd',
+    themeVariables: ['@layout-body-background'],
+  },
});


src/pages/User/login/index.tsx

+    <Button type="primary" onClick={() => {
+     window.less.modifyVars({
+       'layout-body-background': '#FF0000'
+     })
+   }}>点击背景色改变</Button>


随意的效果

1.gif


总结

我的水文总是不能缺了总结,这个方案还是挺有趣的,跑方案的时候,发现很多有趣的问题,写文章的时候,倒觉得都挺简单的了。这个方案从开始收到项目组需求到最终可用,总共花了4天时间,新发了三个包。

欢迎大家试用,欢迎讨论。


源码

【umi 插件】: https://github.com/alitajs/plugin-theme

【从 umi.css 中生成 less 文件】 : https://github.com/alitajs/umi-theme-generator

【webpack 插件,主要作用是取到 umi.css 文件】: https://github.com/alitajs/umi-theme-webpack-plugin


参考链接

【Ant Design Runtime Theme Update 】: https://github.com/ant-design/ant-design/issues/10007

【antd-theme-generator】 : https://github.com/mzohaibqc/antd-theme-generator

【antd-theme-webpack-plugin】: https://github.com/mzohaibqc/antd-theme-webpack-plugin


目录
相关文章
|
6月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的怀旧唱片售卖系统附带文章和源代码设计说明文档ppt
基于ssm+vue.js+uniapp小程序的怀旧唱片售卖系统附带文章和源代码设计说明文档ppt
37 3
|
1月前
|
数据可视化 前端开发 数据安全/隐私保护
DIY可视化快速组件CSS样式设计生成源码
DIY可视化快速组件CSS样式设计生成源码
30 0
|
6月前
|
JavaScript Java 测试技术
基于小程序的外卖点餐+springboot+vue.js附带文章和源代码设计说明文档ppt
基于小程序的外卖点餐+springboot+vue.js附带文章和源代码设计说明文档ppt
41 2
|
6月前
|
Java 关系型数据库 MySQL
springboot+vue外卖点餐系统(源码+文档)
基于SpringBoot的外卖点餐系统包括管理员、用户、商家和骑手四个角色的功能模块。系统采用Java开发,使用SpringBoot框架,JDK1.8,MySQL 5.7+数据库。管理员功能涉及用户、商家、菜品分类、骑手和系统管理等,用户可进行订单、配送单、商品评价和收藏管理。商家和骑手也有相应的订单、配送单和评价管理。此外,提供各类Java毕设项目,涵盖多种框架。项目源码及更多信息可联系风歌获取。
|
5月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的外卖点餐系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的外卖点餐系统的详细设计和实现(源码+lw+部署文档+讲解等)
116 0
Vue3 小兔鲜:Layout-静态模版结构搭建
Vue3 小兔鲜:Layout-静态模版结构搭建
81 0
|
6月前
|
小程序 JavaScript
【微信小程序】之自定义四宫格(不用mp-grids扩展组件实现,这个组件太难用了)
【微信小程序】之自定义四宫格(不用mp-grids扩展组件实现,这个组件太难用了)
|
前端开发 JavaScript
【React工作记录八十】 一步步教你用taro封装一个公司库的下拉组件
【React工作记录八十】 一步步教你用taro封装一个公司库的下拉组件
109 0
|
前端开发
Concis组件库 | 暗黑模式设计
Concis已开工半年时间,开源免费,欢迎大家体验、一起折腾。你可以通过以下方式来支持作者。
110 1
Concis组件库 | 暗黑模式设计
|
前端开发 JavaScript 物联网
React组件库Concis | 组件突破50+,移动端concis起步,新增英语文档,持续更新中...
对于不熟悉这个项目的小伙伴们做个简单的介绍,Concis是一个基于React+TypeScript开发的一款轻量级组件库,全面拥抱React生态,支持React新特性(hooks/redux)追求轻量的组件体积,简单的使用方式,最小的思维负担。
112 1
React组件库Concis | 组件突破50+,移动端concis起步,新增英语文档,持续更新中...