IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> elementUI、ant-design-vue 多套主题下 按需引入和全局引入 的换肤方案 -> 正文阅读

[JavaScript知识库]elementUI、ant-design-vue 多套主题下 按需引入和全局引入 的换肤方案

**组件库多套主题下换肤的关键问题:按需引入时是不支持动态换肤的**

网上也有相关问题的讨论[# 按需引入elementui 导致动态换肤主题被默认主题替换](https://github.com/PanJiaChen/vue-element-admin/issues/2607)

## 组件库按需引入的原理
组件库`按需引入`的原理是相似的:最终只引入指定组件和对应的样式

elementUI需要借助[babel-plugin-component](https://github.com/QingWei-Li/babel-plugin-component) ??
ant-design-vue需要借助[babel-plugin-import](https://github.com/ant-design/babel-plugin-import)?

以`babel-plugin-component`为例 ?
按需引入elementUI的Button组件: ?
```
import { Button } from 'element-ui'

Vue.component(Button.name, Button)
```
编译后的文件(自动引入button.css):?
```
import _Button from "element-ui/lib/button";
import _Button2 from "element-ui/lib/theme-chalk/button.css";
// base.css是公共的样式,如图标和淡入淡出的动画
import "element-ui/lib/theme-chalk/base.css";

Vue.component(_Button.name, _Button);
```
通过按需引入来减少组件库的体积大小

## 组件库按需引入的换肤问题

**为什么说组件库按需引入时是不支持动态换肤的?** ?

**原因1**:按需引入,最终只会引入指定组件的样式,如上文的button.css,但`button.css文件中只有一套样式` ?
**原因2**:按需引入,组件的js和css是在路由懒加载时加载的,会导致动态换肤的主题样式会被按需引入的`默认组件样式给覆盖掉`

![button.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7b4dcd10efa84d1ab9bb424ef802cacc~tplv-k3u1fbpfcp-watermark.image?)

**瓶颈:** ?
elementUI和ant-design-vue均提供了多种自定义主题的方式,但无论何种方式,最终生成的button.css只有一套样式

## 多套主题下,组件库按需引入的换肤方案

**按需引入换肤的解决方案:组件.css文件中包含多套主题样式**

**换肤过程:** ?
1、给css文件扩展命名空间,合并生成的css文件中包含多套主题样式 ??
2、给body加上对应的类名,通过改变body的类名实现组件换肤

增加扩展命名空间的button.css文件

![two.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0e1c252d2c354f6887ef1db30da051c8~tplv-k3u1fbpfcp-watermark.image?)

按需引入的换肤示例:

![demo.gif](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/563c944d20ac4f9f82d3fc47545a9606~tplv-k3u1fbpfcp-watermark.image?)

## 给css文件扩展命名空间

[gulp-css-wrap](https://blog.csdn.net/young_Emily/article/details/78596219) 给css文件所有选择器添加命名空间

**基本用法:** ?
// 安装gulp 和 gulp-css-wrap ?
npm install gulp gulp-css-wrap ??

```
const gulp = require('gulp');
const cssWrap = require('gulp-css-wrap');

gulp.task('css-wrap', function () {
? return gulp.src(`./src/*.css`) // 选择文件
? ? ? .pipe(cssWrap({
? ? ? ? selector: '.LightTheme' /* 添加.LightTheme命名空间 */
? ? ? }))
? ? ? .pipe(gulp.dest('./src')) /* 存放的目录 */
})
```

转化前
```
.el-button + .el-button {
? ? margin-left: 10px
}
.el-button:focus, .el-button:hover {
? ? color: #409EFF;
? ? border-color: #c6e2ff;
? ? background-color: #ecf5ff
}
```
转化后
```
.LightTheme .el-button + .el-button {
? ? margin-left: 10px
}

.LightTheme .el-button:focus, .LightTheme .el-button:hover {
? ? color: #409EFF;
? ? border-color: #c6e2ff;
? ? background-color: #ecf5ff
}
```

## 批量给elementUI的css文件扩展命名空间

打开elementUI源码

### 原始的样式打包流程

package.json中的 `build:theme` 命令用来打包生成组件库的样式

1、执行`node build/bin/gen-cssfile`在`packages/theme-chalk/src目录下生成index.scss`文件 ?
2、执行`packages/theme-chalk/gulpfile.js` 将scss文件编译成 css 并输出到`packages/theme-chalk/lib 目录` ?
3、执行`cp-cli packages/theme-chalk/lib lib/theme-chalk`,将`packages/theme-chalk 拷贝到 lib/theme-chalk目录`

```
// 构建样式: 生成在index.scss && 通过 gulp 将 scss 文件编译成css并输出到packages/theme-chalk/lib目录 && 将生成的样式copy到lib/theme-chalk
"build:theme": "node build/bin/gen-cssfile && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
```

### 增加扩展命名空间的打包流程

#### 1、创建不同主题的scss文件
`packages/theme-chalk/src/common`目录下

创建**浅色**和**红色**两种主题的scss文件

![theme.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac16da3a7fa04e079c4989f805ff8396~tplv-k3u1fbpfcp-watermark.image?)

#### 2、打包命令增加 浅色主题的 变量 `cross-env THEME=Light`
```
"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
```

#### 3、安装插件npm install gulp-css-wrap gulp-concat

修改 `packages/theme-chalk/gulpfile.js`

```
'use strict';

const {series, src, dest} = require('gulp');
const sass = require('gulp-dart-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
const concat = require('gulp-concat');
const cssWrap = require('gulp-css-wrap');
const fs = require('fs');

// 获取当前主题
let theme = process.env.THEME;
// 定义css扩展命名空间
let themeClass = `.${theme}Theme`;

// 删除var.scss, 重新创建var.scss,将LightTheme.scss的内容写入其中
// 用LightTheme.scss 替换 var.scss
function init() {
? fs.unlinkSync('./src/common/var.scss'); // 删除var.scss
? return src(`./src/common/${theme}Theme.scss`)
? ? .pipe(concat('var.scss')) // 定义生成的文件名
? ? .pipe(dest('./src/common'));
}

// 将 scss 编译成 css 并压缩,最后输出到对应主题的目录下
function compile1() {
? return src('./src/*.scss')
? ? .pipe(sass.sync().on('error', sass.logError))
? ? .pipe(autoprefixer({
? ? ? overrideBrowserslist: ['ie > 9', 'last 2 versions'],
? ? ? cascade: false
? ? }))
? ? .pipe(cssmin())
? ? .pipe(dest(`./${theme}`));
}

// 将Light/Light.css copy 到packages/theme-chalk/lib目录下
// 如果使用者不需要换肤,只需单独引入浅色主题.css即可(在全局引入时会用到)
function copyIndex() {
? return src(`./${theme}/index.css`)
? ? .pipe(concat(`${theme}.css`)) // 生成新的文件名, 如Light.css
? ? .pipe(dest('./lib'));
}

// 批量为css文件扩展命名空间 添加.LightTheme前缀
// base.css 和 icon.css 不需要换肤,不用添加前缀
function compile2() {
? // 排除不需要加扩展名base.css 和 icon.css的文件
? return src([`./${theme}/*.css`, `!./${theme}/base.css`, `!./${theme}/icon.css`])
? ? .pipe(cssWrap({
? ? ? selector: themeClass /* 添加的命名空间 */
? ? }))
? ? .pipe(cssmin())
? ? .pipe(dest(`./${theme}`));
}

exports.build = series(init, compile1, copyIndex, compile2);
```

#### 4、执行 `npm run build:theme`, 生成浅色主题的样式

![Light.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f8a14f157bde42bd99ddafe839ad9f6f~tplv-k3u1fbpfcp-watermark.image?)

展开Light/button.css, 浅色主题的扩展名已加上
![LightBtn.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/018e3ed2d008432fab39ef95b4873169~tplv-k3u1fbpfcp-watermark.image?)

**注意:** ?
打包之前,要删除`packages/theme-chalk/src`目录中内容为空的scss文件 ?
否则会报以下错误

![cssgulp报错.png](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/917834d763324b9097ce186406da64e7~tplv-k3u1fbpfcp-watermark.image?)

空的scss文件有
```
// 空的scss文件目录
['breadcrumb-item', 'button-group', 'checkbox-button', 'checkbox-group', 'collapse-item', 'infiniteScroll',
? 'dropdown-item', 'dropdown-menu', 'form-item', 'infinite-scroll', 'menu-item', 'menu-item-group', 'submenu', 'tab-pane' ];
```

#### 5、一键打包生成多套主题

打包命令增加 红色主题的 配置和变量 `cross-env THEME=Red`
```
"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cross-env THEME=Red gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
```
执行`npm run build:theme`, 一键生成浅色和红色 两种主题样式

生成**Light**和**Red**两个文件夹 ?

展开`Red/button.css`,红色主题的扩展名已加上
![Red.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a8985fe2a18e4e1282d411347c7ef29e~tplv-k3u1fbpfcp-watermark.image?)

#### 6、合并多套主题样式

**合并的规则:将不同主题文件夹中,相同文件名的内容写入到一个css文件中**

如Light/button.css 和 Red/button.css → 写入到最终的button.css中

**打包命令增加合并的流程**
```
"build:theme": "node build/bin/gen-cssfile && cross-env THEME=Light gulp build --gulpfile packages/theme-chalk/gulpfile.js && cross-env THEME=Red gulp build --gulpfile packages/theme-chalk/gulpfile.js && gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk",
```

修改 `packages/theme-chalk/gulpfile.js`,增加合并的流程

```
'use strict';

const {series, src, dest} = require('gulp');
const sass = require('gulp-dart-sass');
const autoprefixer = require('gulp-autoprefixer');
const cssmin = require('gulp-cssmin');
const concat = require('gulp-concat');
const cssWrap = require('gulp-css-wrap');
const fs = require('fs');

// 获取当前主题
let theme = process.env.THEME;
// 定义css扩展命名空间
let themeClass = `.${theme}Theme`;

// 删除var.scss, 重新创建var.scss,将Theme.scss的内容写入到var.scss
function init() {
? fs.unlinkSync('./src/common/var.scss'); // 删除var.scss
? return src(`./src/common/${theme}Theme.scss`)
? ? .pipe(concat('var.scss')) // 合并后的文件名
? ? .pipe(dest('./src/common'));
}

// 将 scss 编译成 css 并压缩,最后输出到对应主题的目录下
function compile1() {
? return src('./src/*.scss')
? ? .pipe(sass.sync().on('error', sass.logError))
? ? .pipe(autoprefixer({
? ? ? overrideBrowserslist: ['ie > 9', 'last 2 versions'],
? ? ? cascade: false
? ? }))
? ? .pipe(cssmin())
? ? .pipe(dest(`./${theme}`));
}

// 将Light/Light.css 或 Red/Red.css copy到lib目录下
function copyIndex() {
? return src([`./${theme}/index.css`])
? ? .pipe(concat(`${theme}.css`)) // 合并后的文件名, 如Light.css
? ? .pipe(dest('./lib'));
}

// 批量为css文件扩展命名空间 添加.LightTheme前缀
// base.css 和 icon.css 不需要换肤,不用添加前缀
function compile2() {
? // 排除不需要加扩展名的文件
? return src([`./${theme}/*.css`, `!./${theme}/base.css`, `!./${theme}/icon.css`])
? ? .pipe(cssWrap({
? ? ? selector: themeClass /* 添加的命名空间 */
? ? }))
? ? .pipe(cssmin())
? ? .pipe(dest(`./${theme}`));
}

function compile(path) {
? return src([`./Light/${path}.css`, `./Red/${path}.css`])
? ? .pipe(concat(`${path}.css`)) // 合并后的文件名
? ? .pipe(dest('./lib'));
}

let Components = require('../../components.json');
Components = Object.keys(Components);

// 添加index, 将Light/index.css和Red/index.css 合并成最终的index.css
Components.push('index');

let concatList = Components.map(item => () => {
? return compile(item);
});

// 拷贝 ./src/fonts 到 ./lib/fonts
function copyfont() {
? return src('./src/fonts/**')
? ? .pipe(cssmin())
? ? .pipe(dest('./lib/fonts'));
}

// 拷贝 base.css 和 icon.css 到 ./lib中
function copyBaseAndIcon() {
? return src(['./Light/base.css', './Light/icon.css'])
? ? .pipe(dest('./lib'));
}

// 有theme属性 走打包流程,否则 走合并流程
let gulpTask = theme ? [init, compile1, copyIndex, compile2] : [...concatList, copyfont, copyBaseAndIcon];
exports.build = series(...gulpTask);
```

执行`npm run build:theme`

lib/button.css中包含多套主题,达到最终效果
![compile.png](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a77a1c127c774b509034996e796e596e~tplv-k3u1fbpfcp-watermark.image?)

按需引入的打包问题已解决,下面说下全局引入的换肤方案

## 全局引入的换肤方案

展开最终生成的`lib/theme-chalk`目录 ?
其中包含3个文件,Light.css 、 Red.css 、 index.css

**Light.css包含浅色主题的全部样式 ?
Red.css包含红色主题的全部样式 ?
index.css包含 浅色主题 和 红色主题 的全部样式**

打开index.css: 上半部分为浅色主题的样式,下半部分为红色主题的样式

![index.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7ac89fde7657456b9c0adacd89729e38~tplv-k3u1fbpfcp-watermark.image?)

**当全局引入 需要换肤时** ?
1、import 'element-ui/lib/theme-chalk/index.css'; ?
2、给body加上对应的类名,通过改变body的类名实现组件换肤

**当全局引入 不需要换肤时,可单独引入对应主题的样式** ?
import 'element-ui/lib/theme-chalk/Light.css'; ?
or ?
import 'element-ui/lib/theme-chalk/Red.css';?


## 总结
从按需引入的原理入手,发现该模式不支持换肤的原因

最终通过增加扩展命名空间的方式来解决,更多的收获是对问题的分析过程

**单个知识点总结:** ?
1)学习了`gulp` 基于流的任务化构建工具 ?
2)`流`:边读边写,减少频繁的 IO 操作,节约内存 ?
3)掌握gulp的部分api : `series, src, dest` ?
4)了解gulp的相关插件 `gulp-cssmin`、`gulp-concat`、`gulp-css-wrap`


## 参考链接 ?
[gulp-css-wrap工具的使用](https://blog.csdn.net/young_Emily/article/details/78596219)?
?

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 01:02:48  更:2022-09-04 01:07:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 11:06:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码