为暗色主题自动化生成样式:一个PostCSS插件的创建之旅

148 阅读5分钟

随着暗色主题在现代网页和应用中越来越受欢迎,如何有效地为现有的样式表添加暗色主题支持,同时保持代码的简洁和可维护。这里将介绍一个基于PostCSS的解决方案,它能自动将特定的CSS属性转换为适用于暗色主题的样式规则,同时还能将flex布局的属性值从'start'转换为'flex-start',确保布局的正确性。本文将一步步构建这个插件,并展示如何在webpack环境中使用它。

1. 需求分析

当前项目中使用的CSS样式表中包含大量用于布局的flex属性,以及针对明亮主题优化的颜色值。我们希望通过一个自动化工具来完成以下任务:

  1. 自动将flex布局属性中的'start'值转换为'flex-start'。
  2. 识别出所有带有'dark-'前缀的CSS属性,并将它们转换为适用于暗色主题的新规则。

2. 插件构建步骤

2.0. 最先要做的事情:避坑

在开发自定义PostCSS插件之前,一个常见但容易被忽视的问题是PostCSS版本的兼容性。PostCSS作为一个强大的CSS处理工具,其插件生态同样丰富,不同的插件可能依赖于PostCSS的不同版本。如果不注意版本兼容性,很可能会陷入因版本不匹配而导致的插件无法正常工作的陷阱。为了避免这一问题,应该先行了解和采取一些关键措施。

参考现有项目

在着手开发自定义插件前,参考已有的、成熟的PostCSS插件项目是一种行之有效的方式。这些项目通常会在package.json中明确指定兼容的PostCSS版本范围。通过观察这些项目的依赖和配置方式,可以对如何设置自己的插件项目有一个初步的了解。

明确指定依赖版本

在自己的插件项目的package.json文件中,应该明确指定对PostCSS的依赖版本。这不仅包括主版本号的兼容性,也包括对次要版本和补丁版本的考虑。例如:

jsonCopy code
"peerDependencies": {
  "postcss": "^8.0.0"
}

这样的配置表明,你的插件需要PostCSS 8.0.0及以上版本,但不兼容PostCSS 9.0.0及以上的主版本更新。使用peerDependencies而非dependencies是因为你的插件将作为项目中其他依赖的同级依赖存在,而不是直接依赖于特定版本的PostCSS。

快速解决方案 -- 吐血推荐

为了高效开发,直接使用yarn add postcss-px2rem-exclude安装这个postcss插件,然后在webpack中配置使用。此插件配置成功并生效之后就可以查看其源码然后照着葫芦画瓢制作自己的插件了,通过这样的方法可以保证找到适合当前项目的postcss插件格式。

2.1. 第一步:设置项目环境

首先,需要创建一个Node.js项目,并安装PostCSS:

npm init -y
npm install postcss --save-dev

2.2. 第二步:编写插件逻辑

我们定义一个函数traverseNodes,它将递归地遍历CSS的AST(抽象语法树),对每个节点执行回调函数,以实现上述需求。

function traverseNodes(nodes, callback) {
  nodes.forEach(node => {
    callback(node);
    if (node.nodes) {
      traverseNodes(node.nodes, callback);
    }
  });
}

postcss.plugin方法中,我们创建插件的主体逻辑,利用traverseNodes来处理CSS节点:

module.exports = postcss.plugin('postcss-less-theme', function (options) {
  return function (css) {
    traverseNodes(css.nodes, node => {
      // 处理flex布局属性和暗色主题属性
    });
  }
});

2.3. 第三步:实现属性转换逻辑

在插件内部,我们添加条件判断和处理逻辑,完成属性的自动转换:

if (node.type === 'decl') {
  // 处理flex布局属性
  if (isFlexLayout && node.value === "start") {
    node.value = "flex-start";
  }

  // 生成暗色主题的新规则
  if (node.prop.startsWith('dark-')) {
    // 创建新规则并插入
  }
}

2.4. 完整的插件代码

const postcss = require('postcss');

/**
 * 递归遍历AST(抽象语法树)节点,对每个节点执行回调函数。
 * 如果节点包含子节点,也会对这些子节点递归执行相同的操作。
 *
 * @param {Array} nodes - 节点数组,代表CSS规则的AST节点。
 * @param {Function} callback - 对每个节点执行的回调函数。
 */
function traverseNodes(nodes, callback) {
  nodes.forEach(node => {
    callback(node);
    // 如果当前节点包含子节点,则递归遍历这些子节点
    if (node.nodes) {
      traverseNodes(node.nodes, callback);
    }
  });
}

/**
 * 使用postcss创建一个插件,该插件名为'postcss-less-theme'。
 * 该插件主要用于两个目的:
 * 1. 将flex布局的属性值从'start'自动转换为'flex-start',以确保CSS的正确性。
 * 2. 自动处理带有'dark-'前缀的CSS属性,生成适用于暗色主题的新规则。
 *
 * @param {Object} options - 插件配置项,暂未使用。
 * @returns {Function} 返回一个函数,该函数接收一个CSS的AST并进行处理。
 */
module.exports = postcss.plugin('postcss-less-theme', function (options) {
  return function (css) {
    // 遍历CSS的AST的所有节点
    traverseNodes(css.nodes, node => {
      // 对于flex布局的'justify-content'和'align-items'属性,将值'start'更改为'flex-start'
      const isFlexLayout = node.prop === "justify-content" || node.prop === "align-items";
      if (node.type === 'decl' && isFlexLayout && node.value === "start") {
        node.value = "flex-start";
      }

      // 对于带有'dark-'前缀的属性,生成适用于暗色主题的新规则
      if (node.type === 'decl' && node.prop.startsWith('dark-')) {
        // 获取去掉'dark-'前缀后的属性名称和值
        const prop = node.prop.replace('dark-', '');
        const value = node.value;

        // 构造新的CSS规则,选择器为`[theme=dark]`加上原父选择器
        const newRule = postcss.rule({
          selector: `[theme=dark] ${node.parent.selector}`
        });
        // 在新规则中添加处理后的属性和值
        newRule.append({ prop, value });

        // 将新规则插入到AST中原父节点之后的位置
        node.parent.parent.insertAfter(node.parent, newRule);

        // 移除原节点,即带有'dark-'前缀的声明
        node.remove();
      }
    });
  }
});

3. 在Webpack中使用插件

在webpack配置文件中,我们可以通过postcss-loader来应用这个插件:

{
  test: /\.css$/,
  use: [
    'style-loader',
    'css-loader',
    {
      loader: 'postcss-loader',
      options: {
        postcssOptions: {
          plugins: [
            require('./path/to/postcss-less-theme')({})
          ]
        }
      }
    }
  ]
}

4. 样式转换示例

假设我们有以下LESS文件:

.flex-container {
  display: flex;
  justify-content: start;
  dark-background: #fff;
}

.dark-background {
  dark-color: #000;
}

处理后,将自动生成适用于暗色主题的CSS规则,并修正flex布局属性:

.flex-container {
  display: flex;
  justify-content: flex-start;
}

[theme=dark] .flex-container {
  background: #fff;
}

[theme=dark] .dark-background {
  color: #000;
}

5. 总结

通过构建这个PostCSS插件,我们不仅简化了为现有样式添加暗色主题支持的过程,还提高了CSS代码的准确性和可维护性。


对了,如果你用的IDE是vscode,极有可能会出现警告波浪线,因为原生css中没有dark-开头的属性,这个时候,还需要对vscode进行配置:

  • 使用快捷键ctrl+,
  • 输入命令less.valid
  • 然后就到下面这个页面了,在这里可以添加一些自定义的样式,并且告诉编辑器不要报错。

image.png

是不是非常简单~