Skip to main content

编写插件

¥Writing plugins

插件是自定义规则和自定义规则集。它们可能支持特定的方法或工具集,适用于非标准构造和功能,或者适用于特定用例。

¥Plugins are custom rules and sets of custom rules. They might support a particular methodology or toolset, apply to non-standard constructs and features, or be for specific use cases.

我们建议你的自定义规则遵守我们的 规则约定

¥We recommend your custom rules adhere to our rule conventions for:

  • 名字

    ¥names

  • 选项

    ¥options

  • 消息

    ¥messages

  • 测试

    ¥tests

  • 文档

    ¥docs

  • metadata

  • 构造特定的解析器

    ¥construct-specific parsers

插件的剖析

¥The anatomy of a plugin

此示例插件不允许在选择器中使用单词 "foo":

¥This example plugin disallows the word "foo" in selectors:

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"
};

const ruleFunction = (primary, secondaryOptions) => {
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);

用法是:

¥The usage would be:

{
"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。你应该记录插件的规则名称(和命名空间),因为用户需要在其配置中使用它们。

¥Your plugin's rule name must be namespaced, e.g. your-namespace/your-rule-name, to ensure it never clashes with the built-in rules. If your plugin provides only a single rule or you can't think of a good namespace, you can use plugin/my-rule. You should document your plugin's rule name (and namespace) because users need to use them in their config.

使用 stylelint.createPlugin(ruleName, ruleFunction) 确保你的插件与其他规则一起正确设置。

¥Use stylelint.createPlugin(ruleName, ruleFunction) to ensure that your plugin is set up properly alongside other rules.

为了使你的插件规则能够与 标准配置格式ruleFunction 一起使用,应接受 2 个参数:

¥For your plugin rule to work with the standard configuration format, ruleFunction should accept 2 arguments:

  • 主要选项

    ¥the primary option

  • 可选的,辅助选项对象

    ¥optionally, a secondary options object

如果你的插件规则支持 autofixing,那么 ruleFunction 还应该接受第三个参数:context

¥If your plugin rule supports autofixing, then ruleFunction should also accept a third argument: context.

ruleFunction 应该返回一个本质上有点 PostCSS 插件 的函数。它需要 2 个参数:

¥ruleFunction should return a function that is essentially a little PostCSS plugin. It takes 2 arguments:

  • PostCSS 根(已解析的 AST)

    ¥the PostCSS Root (the parsed AST)

  • PostCSS LazyResult

你必须 了解 PostCSS API

¥You'll have to learn about the PostCSS API.

异步规则

¥Asynchronous rules

你可以将插件编写为异步函数来处理 Promise

¥You can write your plugin as an async function to deal with 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...
};
};

测试

¥Testing

你应该使用 jest-preset-stylelint 来测试你的插件。该预设公开了一个全局 testRule 函数,你可以使用该函数使用模式有效地测试你的插件。

¥You should use jest-preset-stylelint to test your plugin. The preset exposes a global testRule function that you can use to efficiently test your plugin using a schema.

例如:

¥For example:

import rule from "./index.js";

const { messages, ruleName } = rule;

testRule({
plugins: ["./index.js"],
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。

¥However, if your plugin involves more than just checking syntax, you can use Stylelint directly.

例如:

¥For example:

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);
});

或者,如果你不想使用 Jest,你可以在 很棒的 Stylelint 中找到更多测试工具。

¥Alternatively, if you don't want to use Jest, you'll find more testing tool in Awesome Stylelint.

stylelint.utils

Stylelint 公开了一些有用的实用程序。

¥Stylelint exposes some useful utilities.

也欢迎你将任何 内部工具 复制到你的插件中。你不应该直接对它们进行 import,因为它们不是公共 API 的一部分,并且可能会在没有警告的情况下更改或删除。

¥You're also welcome to copy any of the internal utils into your plugin. You should not import them directly, as they are not part of the public API and may change or be removed without warning.

stylelint.utils.report()

将插件中的问题添加到 Stylelint 将向用户报告的问题列表中。

¥Adds problems from your plugin to the list of problems that Stylelint will report to the user.

使用 stylelint.utils.report() 确保你的插件尊重禁用范围和 Stylelint 未来可能的其他功能。

¥Use stylelint.utils.report() to ensure your plugin respects disabled ranges and other possible future features of Stylelint.

IMPORTANT

不要直接使用 PostCSS 的 Node#warn() 方法。

¥Do not use PostCSS's Node#warn() method directly.

你可以指定 PostCSS 的 警告选项(例如 wordindexstart 等)来详细说明问题发生的位置。

¥You can specify PostCSS's warning options (e.g., word, index, start, etc.) to detail where a problem occurs.

stylelint.utils.ruleMessages()

根据标准 Stylelint 规则的格式定制你的消息。

¥Tailors your messages to the format of standard Stylelint rules.

stylelint.utils.validateOptions()

验证你的规则的选项。

¥Validates the options for your rule.

stylelint.utils.checkAgainstRule()

根据你自己的规则中的标准或自定义 Stylelint 规则检查 CSS。此函数为希望修改、限制或扩展现有 Stylelint 规则功能的插件作者提供了强大的功能和灵活性。

¥Checks CSS against a standard or custom Stylelint rule within your own rule. This function provides power and flexibility for plugins authors who wish to modify, constrain, or extend the functionality of existing Stylelint rules.

NOTE

这是一个异步函数。你的自定义规则可能需要等待函数返回的 Promise 得到解析。

¥This is an async function. Your custom rule may need to wait until a Promise returned by the function is resolved.

它接受一个选项对象和一个回调,该回调是通过来自指定规则的警告来调用的。选项有:

¥It accepts an options object and a callback that is invoked with warnings from the specified rule. The options are:

  • ruleName:你正在调用的规则的名称

    ¥ruleName: the name of the rule you are invoking

  • ruleSettings:你调用的规则的设置

    ¥ruleSettings: settings for the rule you are invoking

  • root:运行此规则的根节点

    ¥root: the root node to run this rule against

  • result?:用于解析和调用自定义规则的 PostCSS 结果

    ¥result?: the PostCSS result for resolving and invoking custom rules

  • context?:你正在调用的规则的 context

    ¥context?: the context for the rule you are invoking

使用警告从你使用 stylelint.utils.report() 报告的插件规则创建新警告。

¥Use the warning to create a new warning from your plugin rule that you report with stylelint.utils.report().

例如,假设你想要创建一个运行 at-rule-no-unknown 的插件,并带有由你选择的预处理器提供的 at 规则的内置例外列表:

¥For example, imagine you want to create a plugin that runs at-rule-no-unknown with a built-in list of exceptions for at-rules provided by your preprocessor-of-choice:

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 对象中使用。这使你可以在现有规则的基础上构建以满足你的特定需求。

¥All of the rule functions are available at the stylelint.rules object. This allows you to build on top of existing rules for your particular needs.

NOTE

stylelint.rules 对象中的每个值都是解析规则函数的 Promise

¥Every value in the stylelint.rules object is a Promise resolving a rule function.

典型的用例是构建规则选项允许的更复杂的条件。例如,你的代码库可能使用特殊的注释指令来自定义特定样式表的规则选项。你可以构建一个插件来检查这些指令,然后使用正确的选项运行适当的规则(或者根本不运行它们)。

¥A typical use-case is to build in more complex conditionals that the rule's options allow for. For example, maybe your codebase uses special comment directives to customize rule options for specific stylesheets. You could build a plugin that checks those directives and then runs the appropriate rules with the right options (or doesn't run them at all).

所有规则共享一个共同的签名。它们是一个接受两个参数的函数:主要选项和次要选项对象。该函数返回一个具有 PostCSS 插件签名的函数,期望将 PostCSS 根和结果作为其参数。

¥All rules share a common signature. They are a function that accepts two arguments: a primary option and a secondary options object. And that function returns a function that has the signature of a PostCSS plugin, expecting a PostCSS root and result as its arguments.

以下是仅当样式表中存在特殊指令 @@check-declaration-no-important 时才运行 declaration-no-important 的插件示例:

¥Here's an example of a plugin that runs declaration-no-important only if there is a special directive @@check-declaration-no-important somewhere in the stylesheet:

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);
};
});

允许主选项数组

¥Allow primary option arrays

如果你的插件可以接受数组作为其主要选项,则必须通过在规则函数上设置属性 primaryOptionArray = true 来指定它。有关更多信息,请查看 "制定规则" 文档。

¥If your plugin can accept an array as its primary option, you must designate this by setting the property primaryOptionArray = true on your rule function. For more information, check out the "Working on rules" doc.

对等依赖

¥Peer dependencies

你应该在插件的 package.jsonpeerDependencies 键中(而不是在 dependencies 键中)表达你的插件可以使用的 Stylelint 版本。这是为了确保不会意外安装不同版本的 Stylelint。

¥You should express, within the peerDependencies key (and not within the dependencies key) of your plugin's package.json, what version(s) of Stylelint your plugin can be used with. This is to ensure that different versions of Stylelint are not unexpectedly installed.

例如,为了表示你的插件可以与 Stylelint 版本 14 和 15 一起使用:

¥For example, to express that your plugin can be used with Stylelint versions 14 and 15:

{
"peerDependencies": {
"stylelint": "^14.0.0 || ^15.0.0"
}
}

插件包

¥Plugin packs

要使单个模块提供多个规则,请导出插件对象数组(而不是单个对象)。

¥To make a single module provide multiple rules, export an array of plugin objects (rather than a single object).

分享插件和插件包

¥Sharing plugins and plugin packs

package.json 中使用 stylelint-plugin 关键字。

¥Use the stylelint-plugin keyword within your package.json.