1.0.0-rc.9 • Published 2 years ago

@mega-apps/postcss-theme-helper v1.0.0-rc.9

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

@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-replacerwebpack-theme-color-replacer,大幅度提升。提升较基线至少 100X
  • 代码基于TDDE2E模式开发,版本迭代,质量有保证

安装

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"],
},

Demo

变更日志

CHANGELOG