PostCSS 插件指南(PostCSS Plugin Guidelines)

¥PostCSS Plugin Guidelines

PostCSS 插件是一个从 PostCSS 解析器接收并通常转换 CSS AST 的函数。

¥A PostCSS plugin is a function that receives and, usually, transforms a CSS AST from the PostCSS parser.

以下规则对于所有 PostCSS 插件都是强制性的。

¥The rules below are mandatory for all PostCSS plugins.

另请参阅 ClojureWerkz 的建议 了解开源项目。

¥See also ClojureWerkz’s recommendations for open source projects.

目录

¥Table of Contents

1. API( API)

1.1 使用 postcss- 前缀的清晰名称(1.1 Clear name with postcss- prefix)

¥1.1 Clear name with postcss- prefix

只需阅读其名称即可清楚该插件的用途。如果你为 CSS 4 Custom Media 编写了一个转译器,那么 postcss-custom-media 将是一个好名字。如果你写了一个插件来支持 mixins,postcss-mixins 将是一个好名字。

¥The plugin’s purpose should be clear just by reading its name. If you wrote a transpiler for CSS 4 Custom Media, postcss-custom-media would be a good name. If you wrote a plugin to support mixins, postcss-mixins would be a good name.

前缀 postcss- 表明该插件是 PostCSS 生态系统的一部分。

¥The prefix postcss- shows that the plugin is part of the PostCSS ecosystem.

对于可以作为独立工具运行的插件来说,这条规则不是强制性的,用户不一定知道它是由 PostCSS 提供支持的 - 例如 RTLCSS自动前缀器

¥This rule is not mandatory for plugins that can run as independent tools, without the user necessarily knowing that it is powered by PostCSS — for example, RTLCSS and Autoprefixer.

1.2.做一件事,并把它做好(1.2. Do one thing, and do it well)

¥1.2. Do one thing, and do it well

不要创建多工具插件。将几个小型的、单一用途的插件打包到一个插件包中通常是更好的解决方案。

¥Do not create multitool plugins. Several small, one-purpose plugins bundled into a plugin pack is usually a better solution.

例如,postcss-preset-env 包含许多小插件,每个 W3C 规范都有一个。cssnano 为其每项优化都包含一个单独的插件。

¥For example, postcss-preset-env contains many small plugins, one for each W3C specification. And cssnano contains a separate plugin for each of its optimization.

1.3.不要使用混入(1.3. Do not use mixins)

¥1.3. Do not use mixins

Compass 等预处理器库提供了带有 mixin 的 API。

¥Preprocessors libraries like Compass provide an API with mixins.

PostCSS 插件是不同的。插件不能只是 postcss-mixins 的一组 mixin。

¥PostCSS plugins are different. A plugin cannot be just a set of mixins for postcss-mixins.

为了实现你的目标,请考虑转换有效的 CSS 或使用自定义 at 规则和自定义属性。

¥To achieve your goal, consider transforming valid CSS or using custom at-rules and custom properties.

1.4.保留 postcsspeerDependencies(1.4. Keep postcss to peerDependencies)

¥1.4. Keep postcss to peerDependencies

由于不同插件中的 postcss 版本不同,AST 可能会被破坏。不同的插件可以使用不同的节点创建者(例如 postcss.decl())。

¥AST can be broken because of different postcss version in different plugins. Different plugins could use a different node creators (like postcss.decl()).

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

最好不要导入 postcss

¥It is better even not to import postcss.

- const { list, decl } = require('postcss')
  module.exports = opts => {
    postcssPlugin: 'postcss-name',
-   Once (root) {
+   Once (root, { list, decl }) {
      // Plugin code
    }
  }
  module.exports.postcss = true

1.5.设置 plugin.postcssPlugin 为插件名称(1.5. Set plugin.postcssPlugin with plugin name)

¥1.5. Set plugin.postcssPlugin with plugin name

插件名称将用于错误消息和警告。

¥Plugin name will be used in error messages and warnings.

module.exports = opts => {
  return {
    postcssPlugin: 'postcss-name',
    Once (root) {
      // Plugin code
    }
  }
}
module.exports.postcss = true

2. 加工( Processing)

¥ Processing

2.1.插件必须经过测试(2.1. Plugin must be tested)

¥2.1. Plugin must be tested

还建议使用像 Travis 这样的 CI 服务来在不同环境中测试代码。你应该(至少)在 Node.js 活跃的长期支持 和当前稳定版本中进行测试。

¥A CI service like Travis is also recommended for testing code in different environments. You should test in (at least) Node.js active LTS and current stable version.

2.2.尽可能使用异步方法(2.2. Use asynchronous methods whenever possible)

¥2.2. Use asynchronous methods whenever possible

例如,使用 fs.writeFile 而不是 fs.writeFileSync

¥For example, use fs.writeFile instead of fs.writeFileSync:

let { readFile } = require('fs').promises

module.exports = opts => {
  return {
    postcssPlugin: 'plugin-inline',
    async Decl (decl) {
      const imagePath = findImage(decl)
      if (imagePath) {
        let imageFile = await readFile(imagePath)
        decl.value = replaceUrl(decl.value, imageFile)
      }
    }
  }
}
module.exports.postcss = true

2.3.使用快速节点扫描(2.3. Use fast node’s scanning)

¥2.3. Use fast node’s scanning

订阅特定节点类型比调用 walk* 方法要快得多:

¥Subscribing for specific node type is much faster, than calling walk* method:

  module.exports = {
    postcssPlugin: 'postcss-example',
-   Once (root) {
-     root.walkDecls(decl => {
-       // Slow
-     })
-   }
+   Declaration (decl) {
+     // Faster
+   }
  }
  module.exports.postcss = true

但是,如果你知道需要什么声明的属性或 at 规则的名称,你可以使扫描速度更快:

¥But you can make scanning even faster, if you know, what declaration’s property or at-rule’s name do you need:

  module.exports = {
    postcssPlugin: 'postcss-example',
-   Declaration (decl) {
-     if (decl.prop === 'color') {
-       // Faster
-     }
-   }
+   Declaration: {
+     color: decl => {
+       // The fastest
+     }
+   }
  }
  module.exports.postcss = true

2.4.为新节点设置 node.source(2.4. Set node.source for new nodes)

¥2.4. Set node.source for new nodes

每个节点都必须有一个相关的 source,以便 PostCSS 可以生成准确的源映射。

¥Every node must have a relevant source so PostCSS can generate an accurate source map.

因此,如果你基于某些现有声明添加新声明,则应该克隆现有声明以保存原始 source

¥So if you add a new declaration based on some existing declaration, you should clone the existing declaration in order to save that original source.

if (needPrefix(decl.prop)) {
  decl.cloneBefore({ prop: '-webkit-' + decl.prop })
}

你还可以直接设置 source,从某个现有节点复制:

¥You can also set source directly, copying from some existing node:

if (decl.prop === 'animation') {
  const keyframe = createAnimationByName(decl.value)
  keyframes.source = decl.source
  decl.root().append(keyframes)
}

2.5.仅使用公共 PostCSS API(2.5. Use only the public PostCSS API)

¥2.5. Use only the public PostCSS API

PostCSS 插件不得依赖未记录的属性或方法,这些属性或方法可能会在任何次要版本中发生更改。公共 API 在 API 文档 中描述。

¥PostCSS plugins must not rely on undocumented properties or methods, which may be subject to change in any minor release. The public API is described in API docs.

3. 依赖( Dependencies)

¥ Dependencies

3.1.使用消息来指定依赖(3.1. Use messages to specify dependencies)

¥3.1. Use messages to specify dependencies

如果插件依赖于另一个文件,则应通过将 dependency 消息附加到 result 来指定:

¥If a plugin depends on another file, it should be specified by attaching a dependency message to the result:

result.messages.push({
  type: 'dependency',
  plugin: 'postcss-import',
  file: '/imported/file.css',
  parent: result.opts.from
})

应使用 dir-dependency 消息类型指定目录依赖。默认情况下,目录中的所有文件(递归地)都被视为依赖。可选的 glob 属性可用于指示仅应考虑与特定 glob 模式匹配的文件。

¥Directory dependencies should be specified using the dir-dependency message type. By default all files within the directory (recursively) are considered dependencies. An optional glob property can be used to indicate that only files matching a specific glob pattern should be considered.

result.messages.push({
  type: 'dir-dependency',
  plugin: 'postcss-import',
  dir: '/imported',
  glob: '**/*.css', // optional
  parent: result.opts.from
})

4. 错误( Errors)

¥ Errors

4.1.对 CSS 相关错误使用 node.error(4.1. Use node.error on CSS relevant errors)

¥4.1. Use node.error on CSS relevant errors

如果由于输入 CSS 而出现错误(例如 mixin 插件中的未知名称),你应该使用 node.error 创建一个包含源位置的错误:

¥If you have an error because of input CSS (like an unknown name in a mixin plugin) you should use node.error to create an error that includes source position:

if (typeof mixins[name] === 'undefined') {
  throw node.error('Unknown mixin ' + name)
}

4.2.使用 result.warn 进行警告(4.2. Use result.warn for warnings)

¥4.2. Use result.warn for warnings

不要使用 console.logconsole.warn 打印警告,因为某些 PostCSS 运行程序可能不允许控制台输出。

¥Do not print warnings with console.log or console.warn, because some PostCSS runner may not allow console output.

Declaration (decl, { result }) {
  if (outdated(decl.prop)) {
    result.warn(decl.prop + ' is outdated', { node: decl })
  }
}

如果 CSS 输入是警告的来源,则插件必须设置 node 选项。

¥If CSS input is a source of the warning, the plugin must set the node option.

5. 文档( Documentation)

¥ Documentation

5.1.用英语记录你的插件(5.1. Document your plugin in English)

¥5.1. Document your plugin in English

PostCSS 插件的 README.md 必须用英文书写。不要害怕你的英语技能,因为开源社区会修复你的错误。

¥PostCSS plugins must have their README.md wrote in English. Do not be afraid of your English skills, as the open source community will fix your errors.

当然,欢迎你用其他语言编写文档;只需适当地命名它们(例如 README.ja.md)。

¥Of course, you are welcome to write documentation in other languages; just name them appropriately (e.g. README.ja.md).

5.2.包括输入和输出示例(5.2. Include input and output examples)

¥5.2. Include input and output examples

插件的 README.md 必须包含示例输入和输出 CSS。一个清晰的例子是描述插件如何工作的最佳方式。

¥The plugin's README.md must contain example input and output CSS. A clear example is the best way to describe how your plugin works.

README.md 的第一部分是放置示例的好地方。请参阅 postcss-opacity 的示例。

¥The first section of the README.md is a good place to put examples. See postcss-opacity for an example.

当然,如果你的插件不转换 CSS,则此指南不适用。

¥Of course, this guideline does not apply if your plugin does not transform the CSS.

5.3.维护变更日志(5.3. Maintain a changelog)

¥5.3. Maintain a changelog

PostCSS 插件必须在单独的文件中描述其所有版本的更改,例如 CHANGELOG.mdHistory.mdGitHub 发布。请访问 保留变更日志 以获取有关如何编写其中之一的更多信息。

¥PostCSS plugins must describe the changes of all their releases in a separate file, such as CHANGELOG.md, History.md, or GitHub Releases. Visit Keep A Changelog for more information about how to write one of these.

当然,你应该使用 SemVer

¥Of course, you should be using SemVer.

5.4.在 package.json 中包含 postcss-plugin 关键字(5.4. Include postcss-plugin keyword in package.json)

¥5.4. Include postcss-plugin keyword in package.json

为 npm 编写的 PostCSS 插件必须在其 package.json 中包含 postcss-plugin 关键字。这个特殊的关键字对于获取有关 PostCSS 生态系统的反馈非常有用。

¥PostCSS plugins written for npm must have the postcss-plugin keyword in their package.json. This special keyword will be useful for feedback about the PostCSS ecosystem.

对于未发布到 npm 的包,这不是强制性的,但如果包格式可以包含关键字,则建议这样做。

¥For packages not published to npm, this is not mandatory, but is recommended if the package format can contain keywords.