本文章是自己从CSDN迁徙的原创文章
2021-12-06更新
提供的vite插件@zougt/vite-plugin-theme-preprocessor,已在v1.4.0反对在线动静主题的实现,比原文中提到的计划六的插件更加易用,具备如下特点:
- 应用老本很低
- 跟ui框架无关,Element-ui、iview、Ant-design 等等等(只有基于 less/sass)都能够
- 不依赖 css3 vars
- 浏览器兼容性良好
- 一个主色带动所有梯度色
- 不须要在线编译,性能优
webpack的插件也将会反对,(不只是wepback,有工夫会扩大到更多的构建工具)。
而预设多主题的实现曾经反对vite和webpack,就是以下的原文:
需要背景
当应用 react + ant-design 或 vue + element-ui 的组合或者其余框架,在进行我的项目开发到一半或者曾经实现开发时,客户方想要退出在线预设主题切换的成果,这时有如下的抉择:
- 计划一:应用 css3 的 Variables(须要思考浏览器反对状况)重新整理源码中的 less 或者 sass 变量,在线批改 css 变量达到切换成果,然而组件库中应用了很多的 less 或者 sass 的色彩函数还只是预处理能力不反对 css 变量编译的,须要做很多的组件款式笼罩解决,这是须要不少的工作量的;
- 计划二:预设多份 less 或者 sass 变量文件,应用 webpack 或 gulp 等构建能力提前将所有的款式(包含组件库的)编译出总的多份 css 文件,在线切换 css 文件达到目标,然而须要对我的项目的所有的 less、sass 的援用模式作调整,对构建环境也需很大的调整,款式与 js 齐全拆散,如果有应用 css modules 是更大的麻烦,而且在开发模式下批改调试款式极其不敌对,还不能敌对地对组件库的的 less 或 sass 按需编译;
- 计划三:(不事实的)采纳 css in js 计划对页面和组件重构;
- 计划四:如果你须要的是在线用色彩面板抉择任意的主题色切换,如果是 less 则能够采纳 less.js 在线编译的能力(不思考性能状况),如果是 sass 则须要后盾服务实时编译 sass,但这些对应用 css modules 的不太敌对解决,在原来的我的项目做改变也要不少的工作量;
- 计划五:如果应用的是 ant-design,能够抉择采纳antd-theme-webpack-plugin、antd-theme-generator、umi-plugin-antd-theme等,这也仅限于 antd;
- 计划六:应用webpack-theme-color-replacer(vite版本对应的是vite-plugin-theme),此办法是能够选任意主题色切换的,并且不须要实时编译的后盾服务,然而应用起来略显简单,像antd-design这类组件库提供了一个主题色生成其余梯度色彩的js办法的,可能分明整个组件库的色彩梯度是如何与主题色关联的,这类就还好一点。还有就是这种做法只实用于色彩值,如果还须要蕴含border-radius、font-size等其余非色彩变量值,用这个形式可能无奈做到,因为主题不只是色彩局部。
以上的计划如果都不适宜你,无妨往下看看
以下是 预设主题 实现计划的原文
为此还有一个计划,更简略,更优雅,更敌对的实用于预设多主题的编译,简直无需批改源码,并且无关框架组件库等,只有是基于 less 和 sass,不局限于 webpack、gulp、vite 等构建工具,就是本文重点介绍的内容:
基于 less 和 sass 的预设多主题编译计划
先看一个效果图
已封装的工具
- 次要的实现就是批改了 less 和 sass 的 render 的编译逻辑,请应用@zougt/some-loader-utils的
getLess
和getSass
办法,代替以后构建环境中的less和sass编译器,目前在 webpack 和 vite 中应用测试过。 - 如需对编译后的主题 css 抽取成独立的文件请看 webpack 插件
@zougt/theme-css-extract-webpack-plugin
。 - 如需 vite 版本的只需 vite 插件@zougt/vite-plugin-theme-preprocessor。
多主题编译示例(以 sass + webpack为例)
在webpack中,只需简略配置sass-loader的属性implementation ,能够间接查看 @zougt/some-loader-utils
有多少个主题变量的文件,就会对通过编译器的 less/sass 文件进行编译多少次,所以 multipleScopeVars 项越多,必然会减少编译工夫,倡议在开发模式
只提供一个变量文件,在须要调试切换主题
或生产模式
时就提供残缺的变量文件个数进行打包。
webpack.config.js
const path = require("path");// const sass = require("sass");const { getSass } = require("@zougt/some-loader-utils");const multipleScopeVars = [ { scopeName: "theme-default", path: path.resolve("src/theme/default-vars.scss"), }, { scopeName: "theme-mauve", path: path.resolve("src/theme/mauve-vars.scss"), },];module.exports = { module: { rules: [ { test: /\.scss$/i, loader: "sass-loader", options: { sassOptions: { // 不应用 getMultipleScopeVars 时,也可从这里传入 multipleScopeVars // multipleScopeVars }, implementation: getSass({ // getMultipleScopeVars优先于 sassOptions.multipleScopeVars getMultipleScopeVars: (sassOptions) => multipleScopeVars, // 可选项 // implementation:less }), }, }, ], },};
主题蕴含的不只是色彩局部
假如目前有两种预设主题的 scss 变量文件
//src/theme/default-vars.scss/***此scss变量文件作为multipleScopeVars去编译时,会主动移除!default以达到变量晋升*同时此scss变量文件作为默认主题变量文件,被其余.scss通过 @import 时,必须 !default*/$primary-color: #0081ff !default;$--border-radius-base: 4px !default;
//src/theme/mauve-vars.scss$primary-color: #9c26b0 !default;$--border-radius-base: 8px !default;
一个组件的 scss
//src/components/Button/style.scss@import "../../theme/default-vars";.un-btn { position: relative; display: inline-block; font-weight: 400; white-space: nowrap; text-align: center; border: 1px solid transparent; background-color: $primary-color; border-radius: $--border-radius-base; .anticon { line-height: 1; }}
编译之后
src/components/Button/style.css
.un-btn { position: relative; display: inline-block; font-weight: 400; white-space: nowrap; text-align: center; border: 1px solid transparent;}.theme-default .un-btn { background-color: #0081ff; border-radius: 4px;}.theme-mauve .un-btn { background-color: #9c26b0; border-radius: 8px;}.un-btn .anticon { line-height: 1;}
在html
中扭转 classname 切换主题,只作用于 html 标签 :
<!DOCTYPE html><html lang="zh" class="theme-default"> <head> <meta charset="utf-8" /> <title>title</title> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body></html>
document.documentElement.className = "theme-mauve";
如果不想 html 的权重 classname,能够应用@zougt/theme-css-extract-webpack-plugin
拆散出独立的主题 css 文件,在线切换主题 css 文件即可:
const toggleTheme = (scopeName = "theme-default") => { let styleLink = document.getElementById("theme-link-tag"); if (styleLink) { // 如果存在id为theme-link-tag 的link标签,间接批改其href styleLink.href = `/${scopeName}.css`; // document.documentElement.className = scopeName; } else { // 不存在的话,则新建一个 styleLink = document.createElement("link"); styleLink.type = "text/css"; styleLink.rel = "stylesheet"; styleLink.id = "theme-link-tag"; styleLink.href = `/${scopeName}.css`; // document.documentElement.className = scopeName; document.head.append(styleLink); }};
应用 Css Modules
如果是模块化的 scss,失去的 css 相似:
.src-components-Button-style_theme-default-3CPvz .src-components-Button-style_un-btn-1n85E { background-color: #0081ff;}.src-components-Button-style_theme-mauve-3yajX .src-components-Button-style_un-btn-1n85E { background-color: #9c26b0;}
理论须要的后果应该是这样:
.theme-default .src-components-Button-style_un-btn-1n85E { background-color: #0081ff;}.theme-mauve .src-components-Button-style_un-btn-1n85E { background-color: #9c26b0;}
如果是webpack,在 webpack.config.js 须要对css-loader
(v4.0+) 的 modules 属性增加 getLocalIdent:
const path = require("path");// const sass = require("sass");const { getSass } = require("@zougt/some-loader-utils");const { interpolateName } = require("loader-utils");function normalizePath(file) { return path.sep === "\\" ? file.replace(/\\/g, "/") : file;}const multipleScopeVars = [ { scopeName: "theme-default", path: path.resolve("src/theme/default-vars.scss"), }, { scopeName: "theme-mauve", path: path.resolve("src/theme/mauve-vars.scss"), },];module.exports = { module: { rules: [ { test: /\.module.scss$/i, use: [ { loader: "css-loader", options: { importLoaders: 1, modules: { localIdentName: process.env.NODE_ENV === "production" ? "[hash:base64:5]" : "[path][name]_[local]-[hash:base64:5]", //应用 getLocalIdent 自定义模块化名称 , css-loader v4.0+ getLocalIdent: ( loaderContext, localIdentName, localName, options ) => { if ( multipleScopeVars.some( (item) => item.scopeName === localName ) ) { //localName 属于 multipleScopeVars 的不必模块化 return localName; } const { context, hashPrefix } = options; const { resourcePath } = loaderContext; const request = normalizePath( path.relative(context, resourcePath) ); // eslint-disable-next-line no-param-reassign options.content = `${hashPrefix + request}\x00${localName}`; const inname = interpolateName( loaderContext, localIdentName, options ); return inname.replace(/\\?\[local\\?]/gi, localName); }, }, }, }, { loader: "sass-loader", options: { implementation: getSass({ // getMultipleScopeVars优先于 sassOptions.multipleScopeVars getMultipleScopeVars: (sassOptions) => multipleScopeVars, // 可选项 // implementation:sass }), }, }, ], }, ], },};
vue-cli创立的工程中应用
最近应用了vue-cli4创立开发工程(2021/8/6)
// 在 vue.config.js 配置很简略 , less版本只需把 getSass 改成 getLess 应用const path = require('path');const { getSass } = require('@zougt/some-loader-utils');const ThemeCssExtractWebpackPlugin = require('@zougt/theme-css-extract-webpack-plugin');const multipleScopeVars = [ { scopeName: 'theme-mauve', name: '木槿', path: 'src/scss/theme-mauve.scss', }, { scopeName: 'theme-cyan', name: '天青', path: 'src/scss/theme-cyan.scss', }, { scopeName: 'theme-default', name: '墨黑', path: 'src/scss/theme-default.scss', },];module.exports = { css: { loaderOptions: { scss: { // 这里的选项会传递给 sass-loader implementation: getSass({ // getMultipleScopeVars优先于 sassOptions.multipleScopeVars getMultipleScopeVars: (sassOptions) => multipleScopeVars.map((item) => { return { ...item, path: path.resolve(item.path) }; }), }), }, }, }, chainWebpack: (config) => { config .plugin('ThemeCssExtractWebpackPlugin') .use(ThemeCssExtractWebpackPlugin, [ { multipleScopeVars, // extract: process.env.NODE_ENV === 'production', extract: false, }, ]); },}
为此通过一段时间的钻研,实现了基于less、sass的(新、旧)我的项目预设主题编译计划,使得此类需要变得很简略,并且兼容性很好,在此作一个分享。