Package Exports
- @mega-apps/postcss-theme-helper
- @mega-apps/postcss-theme-helper/unplugin
Readme
@mega-apps/postcss-theme-helper
PostCSS 主题助手
是基于PostCSS框架提供的PostCSS插件,主要解决主题切换的问题。
特性
- 支持主题颜色替换
- (路线)支持主题字体替换
- (路线)支持主题布局替换
- (路线)支持主题特效(动画)替换
- 与前端JS框架无关(支持React, Vue2/3, Angular, SolidJS ...)
- 支持对Vue ElementUI Ver2.x 组件的主题颜色替换(可选用:内置预设)
- 支持对Vue ElementUI-Plus Ver1.x 组件的主题颜色替换(可选用:内置预设)
- 支持对Vue Ant Design Ver1.x 组件的主题颜色替换(可选用:内置预设)
- 支持多主题组颜色替换及单独颜色替换
- 支持CSS选择器过滤多种方式:如:数组包含,正则匹配,glob模式匹配,函数调用
- 支持初始化默认匹配颜色,用于替换默认颜色
- 支持针对颜色值的严格模式和非严格模式指定,例如:匹配颜色,rgb(255,255,255),rgba(255,255,255,0.5)在严模式下,只会处理完全匹配的一种,使用非严格模式,都会处理。
- 支持指定挂载的DOM节点的选择器,
- 支持指定颜色变量的包裹器,如:
:root
,div
,.class
,#id
等; - 支持排除指定的CSS选择器
- 支持排除指定的颜色CSS属性,如:
color
,background-color
,border-color
- 支持调试模式,调试模式下,会在控制台输出颜色变更的信息
- 提供
Webpack
插件,自动注入主题替换代码 - 提供
Vite
插件,自动注入主题替换代码 - 性能较
@mega-apps/webpack-theme-color-replacer
,webpack-theme-color-replacer
,大幅度提升。提升较基线至少100X
- 代码基于
TDD
、E2E
模式开发,版本迭代,质量有保证
安装
yarn add @mega-apps/postcss-theme-helper --dev
# 或使用 pnpm
pnpm add -D @mega-apps/postcss-theme-helper
使用说明
Vite 项目
配置示例
vite.config.js
文件// vite.config.js import { defineConfig } from 'vite'; // 引入主题助手Unplugin import themePluginBuilder from '@mega-apps/postcss-theme-helper/unplugin'; // 定义配置 export default defineConfig({ // 加载主题的vite插件 plugins: [themePluginBuilder.vite({debug: false})], });
postcss.config.js
文件
const themePlugin = require("@mega-apps/postcss-theme-helper");
const themeOptions = {
debug: false,
useShareColorVariableReplaceBuilder: true,
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,最小优先级最高
{
matchColors: ["#409EFF","rgb(239, 68, 68)"],
matchSelectors: [{ type: "RegExpMatch", data: [/.custom/i] }],
initVarColors: ["red","blue"],
strict: true,
cssVarPrefix: "--eColor",
wrapper: ":root",
debug: false,
},
// {
// matchColors: ["black" ],
// matchSelectors: [{ type: "RegExpMatch", data: [/.my-color/i] }],
// initVarColors: ["yellow"],
// strict: true,
// cssVarPrefix: "--hiColor",
// wrapper: "div",
// },
// {
// matchColors: ["#0ea8d0"],
// matchSelectors: [],
// initVarColors: ["green"],
// strict: true,
// cssVarPrefix: "--antColor",
// }
],
}
module.exports = {
plugins: [require('tailwindcss'), require('autoprefixer'), themePlugin.default(themeOptions)],
};
Nuxt.js 项目
配置实例1
// nuxt.config.js
build: {
plugins: [
// 引入自动注入代码
require('@mega-apps/postcss-theme-helper/unplugin').default.webpack({debug: false})
],
postcss: {
postcss: {
plugins: {
"@mega-apps/postcss-theme-helper": {
debug: false,
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,最小优先级最高
{
matchColors: ["#409EFF","rgb(239, 68, 68)"],
matchSelectors: [],
initVarColors: ["red","blue"],
strict: true,
cssVarPrefix: "--eColor",
wrapper: ":root",
},
{
matchColors: ["black" ],
matchSelectors: [{ type: "RegExpMatch", data: [/.my-color/i] }],
initVarColors: ["yellow"],
strict: true,
cssVarPrefix: "--hiColor",
wrapper: "div",
},
{
matchColors: ["#0ea8d0"],
matchSelectors: [],
initVarColors: ["green"],
strict: true,
cssVarPrefix: "--antColor",
}
],
// 预设值
colorVariableReplacerPresets: ['VueElementUI_V2', 'VueAntdUI_V1']
},
},
// 调整postcss插件的次序,参照文档:https://nuxtjs.org/docs/configuration-glossary/configuration-build#postcss
// 插件必须在 tailwindcss 之后,这样才能对tailwindcss的样式进行处理替换
order: ["tailwindcss", "@mega-apps/postcss-theme-helper"],
},
}
}
浏览器客户端
JS 代码示例
/**
* 获得全局主题样式助手
*/
export function getThemeHelper() {
// @ts-ignore
return window["@mega-apps/theme-helper"];
}
/**
* 获得主题颜色的配置
* @param key 唯一ID,一般特指挂载在Style中的id
* @returns {Object}
*/
export function getColorThemeConfig(key = "config-for-theme") {
const themeHelper = getThemeHelper();
if (themeHelper) {
const configMap = themeHelper.getColorThemeConfigMap();
if (configMap) {
return configMap[key];
}
}
return {};
}
/**
* 获得主题颜色的详细的配置项,组Key列表
* @param key
* @returns
*/
export function getColorConfigGroupKeys(key = "config-for-theme") {
const config = getColorThemeConfig(key);
return Object.keys(config);
}
/**
* 改变指定GroupKey的主题颜色
* @note:groupKey = `wrapper + "#*#" + cssVarPrefix` 的格式
*/
export function changeColorsWithGroupKey(
groupKey,
configKey = "config-for-theme"
) {
const themeHelper = getThemeHelper();
console.log("themeHelper = ", themeHelper);
const config = getColorThemeConfig(configKey);
const { themeChange } = themeHelper;
// 获得参数设置的匹配颜色
const matchColors = config[groupKey]?.options?.matchColors || [];
const rColor = () => Math.floor(Math.random() * 256);
// 生成随机替换颜色
const withColors = [];
matchColors.forEach(() => {
const newColor = `rgb(${rColor()}, ${rColor()}, ${rColor()})`;
withColors.push(newColor);
});
// 根据匹配的颜色,随机生成替换的颜色
themeChange.changeColors({
matchColors,
withColors,
// filterWrappers: [":root"], // 过滤包裹器的样式
// filterCssVarPrefixes: [], // 过滤指定的cssVarPrefix
debug: true,
});
}
window.changeColorsWithGroupKey = changeColorsWithGroupKey;
// 调用示例
changeColorsWithGroupKey(':root#*#--eColor');
Typescript 代码示例
获得全局主题样式助手
/**
* 获得全局主题样式助手
*/
export function getThemeHelper(): any {
// @ts-ignore
return window["@mega-apps/theme-helper"];
}
获得主题颜色的配置
/**
* 获得主题颜色的配置
* @param key 唯一ID,一般特指挂载在Style中的id
* @returns {Object}
*/
export function getColorThemeConfig(key = "config-for-theme"): any {
const themeHelper = getThemeHelper();
if (themeHelper) {
const configMap = themeHelper.getColorThemeConfigMap();
if (configMap) {
return configMap[key];
}
}
return {};
}
获得主题颜色的详细的配置项,组Key列表
/**
* 获得主题颜色的详细的配置项,组Key列表
* @param key
* @returns
*/
export function getColorConfigGroupKeys(key = "config-for-theme"): string[] {
const config = getColorThemeConfig(key);
return Object.keys(config);
}
改变指定GroupKey的主题颜色
/**
* 改变指定GroupKey的主题颜色
* @note:groupKey = `wrapper + "#*#" + cssVarPrefix` 的格式
*/
export function changeColorsWithGroupKey(
groupKey: string,
configKey = "config-for-theme"
) {
const themeHelper = getThemeHelper();
const config = getColorThemeConfig(configKey);
const { themeChange } = themeHelper;
// 获得参数设置的匹配颜色
const matchColors: string[] = config[groupKey]?.options?.matchColors || [];
const rColor = () => Math.floor(Math.random() * 256);
// 生成随机替换颜色
const withColors: string[] = [];
matchColors.forEach(() => {
const newColor = `rgb(${rColor()}, ${rColor()}, ${rColor()})`;
withColors.push(newColor);
});
// 根据匹配的颜色,随机生成替换的颜色
themeChange.changeColors({
matchColors,
withColors,
// filterWrappers: [":root"], // 过滤包裹器的样式
// filterCssVarPrefixes: [], // 过滤指定的cssVarPrefix
debug: true,
});
}
参数说明
Webpack 注入插件参数
/**
* 主题注入代码选项
*/
export interface IInjectThemeCodeOptions {
/**
* 注入样式元素的唯一ID值
*
* @example
*
* ``` html
* <html>
* <head>
* <style id="theme-style-element" type="text/css">
* :root {--myColor-cef4444-b: 18;--myColor-cef4444-g: 10;--myColor-cef4444-r: 239;--myColor-cef4444: blue;--myColor-c000000: red;}div {--hiColor-c000000: yellow;}
* </style>
* </head>
* <body></body>
* </html>
* ```
* 关联主题样式的元素ID 为 "theme-style-element";
*/
injectStyleElementId: string;
/**
* 挂载在window对象上的变量名称
*
* @example
*
* ``` typescript
* const windowGlobVarName = "___HELP";
*
* // 最终的结果是 window.___HELP 将被占用赋值
* ```
*/
windowGlobVarName?: string;
}
主题颜色替换参数
参数接口原型
export enum EMatchFilterItemType {
ArrayIncludes = "ArrayIncludes", // 数组包含
RegExpMatch = "RegExpMatch", // 正则匹配
GlobMatch = "GlobMatch", // glob匹配
Function = "Function", // 函数调用
}
export type TMatchFilterItemType =
| EMatchFilterItemType
| keyof typeof EMatchFilterItemType
| [EMatchFilterItemType.ArrayIncludes, EMatchFilterItemType.RegExpMatch];
export type TMatchBaseSupportFilter =
| string
| readonly string[]
| RegExp
| readonly RegExp[];
export interface IMatchFilterBaseItem {
type: TMatchFilterItemType;
data: TMatchBaseSupportFilter;
}
export type TMatchSupportFilterBuilder = (
...args: any[]
) => IMatchFilterBaseItem[];
export type TMatchSupportFilter =
| TMatchBaseSupportFilter
| TMatchSupportFilterBuilder;
export interface IMatchFilterItem extends IMatchFilterBaseItem {
data: TMatchBaseSupportFilter;
}
export enum EColorVariablesReplacerPreset {
VueElementUI_V2, // vue element-ui v2.x 系列
VueElementUI_Plus_V1, // vue element-ui plus v1.x 系列
VueAntdUI_V1, // vue antd (ant-design-vue) v1.x 系列
VueAntdUI_V3, // vue antd (ant-design-vue) v3.x 系列
}
export type TColorVariablesReplacerPresetName = keyof typeof EColorVariablesReplacerPreset;
/**################### 主题替换 ####################**/
/**
* 主题配置选项
*/
export interface IThemeHelperOptions {
/**
* 是否输出调试日志信息
* @default false, 不输出调试日志信息
* */
debug?: boolean;
/**
* 是否开启性能跟踪
* @default false, 不开启性能跟踪
*/
tracePerformance?: boolean;
/**
* 是否使用共享的颜色变量替换构建器(单一实例)
*/
useShareColorVariableReplaceBuilder?: boolean;
/**
* 颜色替换, 支持单一配置,多个配置, 也支持关闭
*/
colorVariableReplacer?:
| IColorVariablesReplacerOptions
| IColorVariablesReplacerOptions[]
| boolean;
colorVariableReplacerPresets?: EColorVariablesReplacerPreset[] | TColorVariablesReplacerPresetName[];
}
示例代码
// 示例代码
plugins: {
"@mega-apps/postcss-theme-helper": {
debug: false,//
// tracePerformance: false,
// useShareColorVariableReplaceBuilder: false,
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,优先级高低:按序降低
{
matchColors: ["black", "rgb(239, 68, 68)"],
initVarColors: ["red", "blue"],
strict: true,
cssVarPrefix: "--myColor",
wrapper: ":root",
},
{
matchColors: ["black"],
initVarColors: ["yellow"],
strict: true,
cssVarPrefix: "--hiColor",
wrapper: "div",
}
],
colorVariableReplacerPresets: [], // VueElementUI_V2, VueElementUI_Plus_V1, VueAntdUI_V1,
}
}
参数:matchColors
特指要匹配的颜色值数组; 这些颜色值将支持主题替换
- 支持颜色值的模式:
- 主要:
- 16进制(3位,4位,6位,8位):#fff, #ffff, #ffffff, #ffffffff
- RGB/RGBA 字符串:rgb(192, 192, 192), rgba(192, 192, 192, 0.5)
- HSL/HSLA 字符串: hsl(0, 50%, 50%), hsl(0, 50%, 50%, 0.25)
- 颜色名称: red, black, blue, green
- 其他:
- HWB 字符串
- CMYK 字符串
- LCH 字符串
- LAB 字符串
- XYZ 字符串
- 主要:
- 自动去重
参数:matchProps
特指需要匹配的包含颜色的属性名称
- 默认值为: [DEFAULT_COLOR_PROPS], 表示所有CSS属性都会被遍历
- 可以指定自己的默认的属性包含, 如:
background
,color
// 注意,匹配模式,要求指定过滤方式,否则无法匹配:如:ArrayIncludes,
matchProps: [{ type: "ArrayIncludes", data: DEFAULT_COLOR_PROPS }] // 只查找css样式中,包含background的规则
参数:excludeProps
特指需要排除的属性名称
- 默认值: [], 表示不排除任何项
- 支持的值形式:字符串数组、正则表达式、正则表达式数组、函数
excludeProps: [{ type: "RegExpMatch", data: [/^border/i] }] // 以border开头的全部排除在外
参数:matchSelectors
特指需要匹配的选择器
- 默认值为: []
- 支持的值形式:字符串数组、正则表达式、正则表达式数组、函数
// 字符串数组形式,使用glob形式,参见:https://www.npmjs.com/package/micromatch
['ele-*']
// 正则表达式形式
/ele/gi
// 正则表达式数组
[/^ele/gi, /^ant/gi]
// 函数形式
(...args) => { return []}
matchSelectors: [{ type: "RegExpMatch", data: [/.my-color/i] }]
参数:excludeSelectors
特指需要排除的CSS选择器
- 默认:[], 表示不排除任何项
- 支持的值形式:字符串数组、正则表达式、正则表达式数组、函数
- 与
matchSelectors
的匹配规则雷同
excludeSelectors: [{ type: "RegExpMatch", data: [/^ele/gi] }] // 以ele开头的全部排除在外
参数:initVarColors
特指:默认初始化颜色, 例如:匹配到 black
,可以让生成的颜色变成 red
默认替换颜色与matchColors成对出现
matchColors: ['black', 'rgb(22,22,22)', 'rgb(44,44,23)']
initVarColors: ['red', 'green']
// 转换结果: black -> red; rgb(22,22,22) -> green; rgb(44,44,23) -> green
参数:strict
特指是否严格模式,如果为true,则忽略alpha的颜色相近处理
- 默认是true
// 设置strict: false
matchColors: ['black']
strict: false
// 结果
// rgba(0,0,0, 0.5) 也会被匹配上
// 设置strict: true
matchColors: ['black']
strict: true
// 结果
// rgba(0,0,0, 0.5) 不会被匹配上
参数:cssVarPrefix
生成的css变量的前缀
/* cssVarPrefix: --myVar */
:root {
--myVar-name: 'black'
}
参数:useMatchColorValueForCssVar
cssVar 使用matchColors中的颜色值
- 默认:true
/* useMatchColorValueForCssVar: true*/
:root {
--myVar-black: 'black'
}
/* useMatchColorValueForCssVar: false */
:root {
--myVar-black: '#000'
}
参数:wrapper
定义的css变量值,外部包装器,例如: :root
/* wrapper: ':root' */
:root {
--myVar-black: 'black'
}
/* wrapper: 'body > div' */
body > div {
--myVar-black: 'black'
}
参数:debug
是否单独输出调试日志
- 默认值:false
示例
启用 Ant Design Vue 主题替换
当前内置预设,支持的 Ant Design Vue
版本是:
- "VueAntdUI_V1": 对应 "ant-design-vue": "^1.x"
- "VueAntdUI_V3": 对应 "ant-design-vue": "^3.x"
// postcss plugins
plugins: {
"@mega-apps/postcss-theme-helper": {
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,优先级高低:按序降低
{
matchColors: ["black", "rgb(239, 68, 68)"],
initVarColors: ["red", "blue"],
strict: true,
cssVarPrefix: "--myColor",
wrapper: ":root",
}
],
colorVariableReplacerPresets: ['VueAntdUI_V1'], // 预设为数组,可以将 'vue-antd-ui.v1' 添加进来
}
}
启用 Element UI 主题替换
当前内置预设,支持的 Element UI
版本是:
- "VueElementUI_V2": 对应 "element-ui": "^2.x"
- "VueElementUI_Plus_V1": 对应 "element-plus": "^1.3.0"
// postcss plugins
plugins: {
"@mega-apps/postcss-theme-helper": {
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,优先级高低:按序降低
{
matchColors: ["black", "rgb(239, 68, 68)"],
initVarColors: ["red", "blue"],
strict: true,
cssVarPrefix: "--myColor",
wrapper: ":root",
preset: ['vue-element-ui.v2'], // 预设为数组,可以将 'vue-element-ui.v2' 添加进来
}
],
colorVariableReplacerPresets: ['VueElementUI_V2'], // 预设为数组,可以将 'element-ui.v2' 添加进来
}
}
启用多个UI库主题替换
// postcss plugins
plugins: {
"@mega-apps/postcss-theme-helper": {
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,优先级高低:按序降低
{
matchColors: ["black", "rgb(239, 68, 68)"],
initVarColors: ["red", "blue"],
strict: true,
cssVarPrefix: "--myColor",
wrapper: ":root"
}
],
colorVariableReplacerPresets: ['VueElementUI_V2', 'VueAntdUI_V1']
}
}
启用多组主题替换
postcss: {
plugins: {
"@mega-apps/postcss-theme-helper": {
useShareColorVariableReplaceBuilder: true, // 开启共享CSS变量
colorVariableReplacer: [
// 类似于CSS的解析,从上而下,优先级高低:按序降低
{
matchColors: ["#409EFF","rgb(239, 68, 68)"],
matchSelectors: [],
initVarColors: ["red","blue"],
strict: true,
cssVarPrefix: "--myColor",
// wrapper: ":root",
},
{
matchColors: ["black" ],
matchSelectors: [{ type: "RegExpMatch", data: [/.my-color/i] }],
initVarColors: ["yellow"],
strict: true,
cssVarPrefix: "--hiColor",
// wrapper: "div"
},
{
matchColors: ["#0ea8d0"],
matchSelectors: [],
initVarColors: ["green"],
strict: true,
cssVarPrefix: "--antColor",
// wrapper: "div"
}
],
colorVariableReplacerPresets: ['VueElementUI_V2', 'VueAntdUI_V1']
},
},
// 调整postcss插件的次序,参照文档:https://nuxtjs.org/docs/configuration-glossary/configuration-build#postcss
// 插件必须在 tailwindcss 之后,这样才能对tailwindcss的样式进行处理替换
order: ["tailwindcss","@mega-apps/postcss-theme-helper"],
},