CSS `Stylelint` 自定义规则:代码规范与质量控制

CSS Stylelint 自定义规则:代码规范与质量控制 (讲座模式)

各位观众老爷们,晚上好!我是你们的老朋友,代码界的段子手,今天跟大家聊聊 CSS Stylelint 自定义规则这档子事儿。保证让你听完之后,腰不酸了,腿不疼了,代码也更规范了!

1. 啥是 Stylelint?别跟我说你不知道!

首先,咱们得先明确一下,Stylelint 是个啥玩意?简单来说,它就是 CSS 代码的“质检员”,负责检查你的 CSS 代码是否符合规范。就像警察蜀黍查酒驾一样,逮住不规范的,直接给你开罚单(报错)。

Stylelint 默认提供了一大堆规则,比如:

  • color-hex-case: 强制十六进制颜色值的大小写。
  • font-family-name-quotes: 强制字体族名称加引号。
  • selector-class-pattern: 限制类选择器的命名模式。

这些规则已经足够应付大部分场景了,但是,总有一些“奇葩”的需求,需要我们自己动手定制规则。 这时候,就轮到自定义规则登场了!

2. 为什么要自定义规则?难道默认的不好用?

默认规则当然好用,但是…

  • 公司有自己的代码规范:每个公司都有自己的“家规”,比如命名规范、属性顺序、注释要求等等。默认规则可能无法完全满足这些需求。
  • 项目有特殊的要求:有些项目比较特殊,需要一些特殊的规则来保证代码质量。
  • 想提高团队效率:通过自定义规则,可以自动检查一些常见的错误,减少 code review 的时间和成本。

举个例子,假设我们公司规定,所有的类名都必须以 prefix- 开头,比如 prefix-buttonprefix-input。默认的 Stylelint 规则就无法满足这个需求,这时候就需要自定义规则了。

3. 自定义规则的原理:AST 是个啥?

要自定义规则,我们得先了解一下 Stylelint 的工作原理。Stylelint 会把你的 CSS 代码解析成一个抽象语法树 (Abstract Syntax Tree, AST)。AST 就像一棵倒过来的树,每个节点代表一个 CSS 语法元素,比如选择器、属性、值等等。

举个例子,对于这段 CSS 代码:

.button {
  color: red;
  font-size: 16px;
}

Stylelint 会把它解析成这样的 AST(简化版):

{
  "type": "stylesheet",
  "stylesheet": {
    "rules": [
      {
        "type": "rule",
        "selectors": [
          {
            "type": "selector",
            "value": ".button"
          }
        ],
        "declarations": [
          {
            "type": "declaration",
            "property": "color",
            "value": "red"
          },
          {
            "type": "declaration",
            "property": "font-size",
            "value": "16px"
          }
        ]
      }
    ]
  }
}

我们的自定义规则,其实就是在 AST 上进行遍历,找到符合特定条件的节点,然后进行检查和报错。

4. 如何编写自定义规则:手把手教你!

接下来,咱们就来手把手地教你如何编写自定义规则。

4.1 准备工作

首先,你需要安装 Stylelint 和 stylelint-webpack-plugin (如果你使用 webpack 的话)。

npm install stylelint stylelint-webpack-plugin --save-dev

然后,在你的项目中创建一个 stylelint.config.js 文件,这是 Stylelint 的配置文件。

module.exports = {
  extends: [
    'stylelint-config-standard', // 继承 Stylelint 官方推荐的规则
  ],
  plugins: [
    './path/to/your/custom-rule.js', // 引入你的自定义规则
  ],
  rules: {
    // 这里可以覆盖或者添加额外的规则
  },
};

4.2 编写规则代码

接下来,创建一个 JavaScript 文件,比如 custom-rule.js,这就是你的自定义规则代码。

const stylelint = require('stylelint');

const ruleName = 'your-prefix/class-name-prefix'; // 规则名称,需要符合命名规范
const messages = stylelint.utils.ruleMessages(ruleName, {
  expected: '类名必须以 "prefix-" 开头',
});

module.exports = stylelint.createPlugin(ruleName, (primaryOption, secondaryOptions) => {
  return (root, result) => {
    const validOptions = stylelint.utils.validateOptions(
      result,
      ruleName,
      {
        actual: primaryOption,
        possible: [true], // 允许 primaryOption 为 true
      },
      {
        actual: secondaryOptions,
        possible: {}, // 允许 secondaryOptions 为空
      }
    );

    if (!validOptions) {
      return;
    }

    root.walkRules((rule) => {
      rule.selectors.forEach((selector) => {
        // 忽略 :global 和 :local 选择器
        if (selector.includes(':global') || selector.includes(':local')) {
          return;
        }

        if (selector.startsWith('.')) {
          const className = selector.slice(1); // 去掉 '.'

          if (!className.startsWith('prefix-')) {
            stylelint.utils.report({
              message: messages.expected,
              node: rule,
              result,
              ruleName,
            });
          }
        }
      });
    });
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

代码解释:

  • stylelint.createPlugin(ruleName, (primaryOption, secondaryOptions) => { ... }): 这是 Stylelint 创建插件的 API。
  • ruleName: 规则的名称,需要符合命名规范,比如 your-prefix/class-name-prefix
  • messages: 错误提示信息。
  • primaryOption: 规则的主要选项,比如 true 或者 false
  • secondaryOptions: 规则的次要选项,比如一些配置参数。
  • root.walkRules((rule) => { ... }): 遍历所有的 CSS 规则。
  • rule.selectors.forEach((selector) => { ... }): 遍历规则中的所有选择器。
  • stylelint.utils.report({ ... }): 报错函数。

4.3 配置 Stylelint

stylelint.config.js 文件中,引入你的自定义规则,并启用它。

module.exports = {
  extends: [
    'stylelint-config-standard',
  ],
  plugins: [
    './custom-rule.js',
  ],
  rules: {
    'your-prefix/class-name-prefix': true, // 启用自定义规则
  },
};

4.4 运行 Stylelint

现在,你可以运行 Stylelint 来检查你的 CSS 代码了。

npx stylelint "**/*.css"

如果你的代码中有不符合规则的类名,Stylelint 就会报错。

5. 进阶技巧:更复杂的规则!

上面的例子只是一个简单的规则,用来检查类名的前缀。实际上,你可以编写更复杂的规则,比如:

  • 检查属性的顺序:强制按照特定的顺序排列 CSS 属性。
  • 限制颜色的使用:只允许使用指定的颜色变量。
  • 检查注释的格式:强制按照特定的格式编写注释。
  • 检查媒体查询的使用:限制媒体查询的嵌套层级。

要编写更复杂的规则,你需要更深入地了解 AST,以及 Stylelint 提供的 API。

5.1 检查属性顺序的例子

const stylelint = require('stylelint');

const ruleName = 'your-prefix/property-order';
const messages = stylelint.utils.ruleMessages(ruleName, {
  expected: (first, second) => `属性 ${first} 应该在 ${second} 之前`,
});

const propertyOrder = [
  'position',
  'top',
  'right',
  'bottom',
  'left',
  'display',
  'width',
  'height',
  'margin',
  'padding',
  'border',
  'font-size',
  'color',
  'background',
];

module.exports = stylelint.createPlugin(ruleName, (primaryOption) => {
  return (root, result) => {
    if (!primaryOption) {
      return;
    }

    root.walkDecls((decl) => {
      const property = decl.prop;
      const index = propertyOrder.indexOf(property);

      if (index === -1) {
        return; // 忽略不在 propertyOrder 中的属性
      }

      let prevIndex = -1;
      decl.prevAll().forEach((prev) => {
        if (prev.type === 'decl') {
          const prevProperty = prev.prop;
          const prevPropertyIndex = propertyOrder.indexOf(prevProperty);

          if (prevPropertyIndex !== -1) {
            prevIndex = prevPropertyIndex;
            return;
          }
        }
      });

      if (prevIndex > index) {
        stylelint.utils.report({
          message: messages.expected(property, propertyOrder[prevIndex]),
          node: decl,
          result,
          ruleName,
        });
      }
    });
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

这个规则会检查 CSS 属性的顺序,如果属性的顺序不符合 propertyOrder 数组中的顺序,就会报错。

5.2 使用 stylelint.utils.walkCompositions 处理 composes

如果你使用了 CSS Modules,并且使用了 composes 语法,那么你需要使用 stylelint.utils.walkCompositions 来处理 composes 引入的类名。

const stylelint = require('stylelint');

const ruleName = 'your-prefix/composes-class-name-prefix';
const messages = stylelint.utils.ruleMessages(ruleName, {
  expected: 'Composes 引入的类名必须以 "prefix-" 开头',
});

module.exports = stylelint.createPlugin(ruleName, (primaryOption) => {
  return (root, result) => {
    if (!primaryOption) {
      return;
    }

    root.walkDecls('composes', (decl) => {
      stylelint.utils.walkCompositions(decl.value, (composition) => {
        const className = composition.value;

        if (!className.startsWith('prefix-')) {
          stylelint.utils.report({
            message: messages.expected,
            node: decl,
            result,
            ruleName,
          });
        }
      });
    });
  };
});

module.exports.ruleName = ruleName;
module.exports.messages = messages;

这个规则会检查 composes 引入的类名,如果类名不以 prefix- 开头,就会报错。

6. Stylelint 插件:偷懒的福音!

如果你不想自己编写自定义规则,也可以使用现成的 Stylelint 插件。有很多优秀的 Stylelint 插件,可以满足各种各样的需求。

常用的 Stylelint 插件:

插件名称 功能
stylelint-config-standard Stylelint 官方推荐的规则集,包含了很多常用的规则。
stylelint-config-rational-order 强制按照特定的顺序排列 CSS 属性。
stylelint-config-idiomatic-order 另一种属性排序规则,更加符合语义化的顺序。
stylelint-declaration-block-no-redundant-longhand-properties 避免使用冗余的长属性,比如 margin-top: 10px; margin-right: 10px; margin-bottom: 10px; margin-left: 10px;,应该使用 margin: 10px;
stylelint-selector-bem-pattern 强制使用 BEM 命名规范。
stylelint-scss 支持 SCSS 语法,提供了一些 SCSS 相关的规则。
stylelint-less 支持 LESS 语法,提供了一些 LESS 相关的规则。

使用插件非常简单,只需要安装插件,然后在 stylelint.config.js 文件中引入插件即可。

module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-rational-order', // 引入属性排序插件
  ],
  plugins: [
    'stylelint-scss', // 引入 SCSS 插件
  ],
  rules: {
    // 这里可以覆盖或者添加额外的规则
    'scss/dollar-variable-pattern': /^[a-z][a-zA-Z0-9]+$/, // 强制 SCSS 变量的命名规范
  },
};

7. 总结:代码规范,人人有责!

好了,今天的讲座就到这里了。希望大家能够掌握 CSS Stylelint 自定义规则的编写方法,提高代码质量,规范团队协作,让我们的代码更加优雅、健壮!

记住,代码规范,人人有责!让我们一起努力,为构建更美好的代码世界贡献自己的力量!

散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注