编写插件
插件是自定义规则和自定义规则集。它们可能支持特定的方法或工具集,适用于非标准结构和功能,或用于特定用例。
我们建议您的自定义规则遵循我们的 规则约定,用于
- 名称
- 选项
- 消息
- 测试
- 文档
- 元数据
- 特定于结构的解析器
插件的结构
此示例插件禁止在选择器中使用“foo”一词
import stylelint from "stylelint";
const {
  createPlugin,
  utils: { report, ruleMessages, validateOptions }
} = stylelint;
const ruleName = "foo-org/selector-no-foo";
const messages = ruleMessages(ruleName, {
  rejected: (selector) => `Unexpected "foo" within selector "${selector}"`
});
const meta = {
  url: "https://github.com/foo-org/stylelint-selector-no-foo/blob/main/README.md"
};
/** @type {import('stylelint').Rule} */
const ruleFunction = (primary, secondaryOptions, context) => {
  return (root, result) => {
    const validOptions = validateOptions(result, ruleName, {
      actual: primary,
      possible: [true]
    });
    if (!validOptions) return;
    root.walkRules((ruleNode) => {
      const { selector } = ruleNode;
      if (!selector.includes("foo")) return;
      report({
        result,
        ruleName,
        message: messages.rejected(selector),
        node: ruleNode,
        word: selector
      });
    });
  };
};
ruleFunction.ruleName = ruleName;
ruleFunction.messages = messages;
ruleFunction.meta = meta;
export default createPlugin(ruleName, ruleFunction);
该 @type JSDoc 注释 使 Typescript 能够自动完成和类型检查。
用法如下
{
  "plugins": ["@foo-org/stylelint-selector-no-foo"],
  "rules": {
    "foo-org/selector-no-foo": true
  }
}
$ echo '.foo {}' | stylelint --stdin-filename=test.css
test.css
 1:1  ✖  Unexpected "foo" within selector ".foo"  foo-org/selector-no-foo
1 problem (1 error, 0 warnings)
您的插件的规则名称必须使用命名空间,例如 your-namespace/your-rule-name,以确保它永远不会与内置规则冲突。如果您的插件只提供一个规则,或者您想不出一个好的命名空间,您可以使用 plugin/my-rule。您应该记录您的插件的规则名称(和命名空间),因为用户需要在他们的配置中使用它们。
使用 stylelint.createPlugin(ruleName, ruleFunction) 来确保您的插件与其他规则一起正确设置。
为了使您的插件规则与 标准配置格式 兼容,ruleFunction 应该接受 2 个参数
- 主要选项
- 可选的,一个辅助选项对象
如果您的插件规则支持 自动修复,那么 ruleFunction 还应该接受第三个参数:context。
ruleFunction 应该返回一个函数,它本质上是一个小的 PostCSS 插件。它接受 2 个参数
- PostCSS 根节点(解析后的 AST)
- PostCSS LazyResult
您需要 了解 PostCSS API。
异步规则
您可以将您的插件编写为一个异步函数来处理 Promise。
const ruleFunction = (primary, secondaryOptions) => {
  return async (root, result) => {
    // validate options...
    // load disallowed words asynchronously
    const disallowedWords = await import("./disallowed-words.js");
    // traverse AST nodes...
    // report a warning if a problem word is detected...
  };
};
测试
您可以使用以下任一方法
- stylelint-test-rule-node(基于- node:test)
- jest-preset-stylelint(基于 Jest)
两者都公开了一个 testRule 函数,您可以使用它来使用模式有效地测试您的插件。
例如
import { testRule } from "stylelint-test-rule-node";
import plugin from "./index.js";
const {
  rule: { messages, ruleName }
} = plugin;
testRule({
  plugins: [plugin],
  ruleName,
  config: true,
  fix: true,
  accept: [
    {
      code: ".a {}"
    },
    {
      code: ".b {}"
    }
  ],
  reject: [
    {
      code: ".foo {}",
      fixed: ".safe {}",
      message: messages.rejected(".foo"),
      line: 1,
      column: 1,
      endLine: 1,
      endColumn: 8
    }
  ]
});
或者,您将在 精选 Stylelint 中找到更多测试选项。
如果您的插件不仅仅是检查语法,您可以直接使用 Stylelint。
例如
import stylelint from "stylelint";
const { lint } = stylelint;
const config = {
  plugins: ["./index.js"],
  rules: {
    "foo-org/selector-no-foo": true
  }
};
it("warns", async () => {
  const {
    results: [{ warnings, parseErrors }]
  } = await lint({
    files: ["fixtures/test.css"],
    config
  });
  expect(parseErrors).toHaveLength(0);
  expect(warnings).toHaveLength(1);
  const [{ text, line, column }] = warnings;
  expect(text).toBe('Unexpected "foo" within selector ".foo"');
  expect(line).toBe(1);
  expect(column).toBe(1);
});
it("doesn't warn", async () => {
  const {
    results: [{ warnings, parseErrors }]
  } = await lint({
    code: ".foo {}",
    config
  });
  expect(parseErrors).toHaveLength(0);
  expect(warnings).toHaveLength(0);
});
stylelint.utils
Stylelint 公开了某些有用的实用程序。
您也可以将任何 内部实用程序 复制到您的插件中。您不应该直接 import 它们,因为它们不是公共 API 的一部分,可能会在没有警告的情况下更改或删除。
stylelint.utils.report()
将来自您的插件的问题添加到 Stylelint 将向用户报告的问题列表中。
使用 stylelint.utils.report() 来确保您的插件尊重禁用的范围和其他 Stylelint 的未来功能。
不要直接使用 PostCSS 的 Node#warn() 方法。
您可以指定 PostCSS 的 警告选项(例如,word、index、start 等)来详细说明问题发生的位置。
stylelint.utils.ruleMessages()
将您的消息定制为标准 Stylelint 规则的格式。
stylelint.utils.validateOptions()
验证您规则的选项。
stylelint.utils.checkAgainstRule()
检查 CSS 是否符合标准或自定义 Stylelint 规则在您自己的规则中。此函数为希望修改、约束或扩展现有 Stylelint 规则功能的插件作者提供了强大而灵活的功能。
这是一个异步函数。您的自定义规则可能需要等待函数返回的 Promise 解析。
它接受一个选项对象和一个回调函数,该回调函数使用指定规则的警告调用。选项是
- ruleName:您正在调用的规则的名称
- ruleSettings:您正在调用的规则的设置
- root:要针对其运行此规则的根节点
- result?:用于解析和调用自定义规则的 PostCSS 结果
- context?:您正在调用的规则的 上下文
使用警告从您的插件规则创建新的警告,并使用 stylelint.utils.report() 报告。
例如,假设您想创建一个插件,该插件运行 at-rule-no-unknown,并使用您选择的预处理器提供的 at-rules 内置异常列表
const {
  utils: { checkAgainstRule, report }
} = stylelint;
const allowableAtRules = [
  /* .. */
];
const ruleName = "your-own/at-rule-no-unknown";
const myPluginRule = (primary, secondaryOptions, context) => {
  return async (root, result) => {
    const ignoreAtRules = allowableAtRules.concat(
      secondaryOptions?.ignoreAtRules ?? []
    );
    const defaultedSecondaryOptions = { ...secondaryOptions, ignoreAtRules };
    await checkAgainstRule(
      {
        ruleName: "at-rule-no-unknown",
        ruleSettings: [primary, defaultedSecondaryOptions],
        root,
        result,
        context
      },
      (warning) => {
        report({
          ruleName,
          result,
          message: warning.text,
          node: warning.node,
          start: { line: warning.line, column: warning.column },
          end: { line: warning.endLine, column: warning.endColumn }
        });
      }
    );
  };
};
stylelint.rules
所有规则函数都可以在 stylelint.rules 对象中找到。这使您可以根据自己的特定需求构建现有规则。
stylelint.rules 对象中的每个值都是一个解析规则函数的 Promise。
一个典型的用例是构建比规则选项允许的更复杂的条件。例如,也许您的代码库使用特殊的注释指令来为特定样式表定制规则选项。您可以构建一个插件来检查这些指令,然后使用正确的选项运行相应的规则(或者根本不运行它们)。
所有规则都共享一个通用签名。它们是一个函数,它接受两个参数:一个主要选项和一个辅助选项对象。该函数返回一个具有 PostCSS 插件签名的函数,期望 PostCSS 根节点和结果作为其参数。
以下是一个插件的示例,该插件仅在样式表中存在特殊指令 @@check-declaration-no-important 时才运行 declaration-no-important
createPlugin(ruleName, (primary) => {
  const rulePromise = stylelint.rules["declaration-no-important"];
  const ruleRunnner = rulePromise.then((rule) => rule(primary));
  return async (root, result) => {
    if (!root.toString().includes("@@check-declaration-no-important")) {
      return;
    }
    (await ruleRunnner)(root, result);
  };
});
允许主要选项数组
如果您的插件可以接受数组作为其主要选项,则必须通过在您的规则函数上设置属性 primaryOptionArray = true 来指定这一点。有关更多信息,请查看 "处理规则" 文档。
对等依赖项
您应该在您的插件的 package.json 中的 peerDependencies 键(而不是 dependencies 键)中表达您的插件可以与哪些版本的 Stylelint 一起使用。这是为了确保不会意外安装不同版本的 Stylelint。
例如,要表达您的插件可以与 Stylelint 版本 14 和 15 一起使用
{
  "peerDependencies": {
    "stylelint": "^14.0.0 || ^15.0.0"
  }
}
插件包
要使单个模块提供多个规则,请导出一个插件对象数组(而不是单个对象)。
共享插件和插件包
在您的 package.json 中使用 stylelint-plugin 关键字。