所以你想制作一个 PostCSS 插件

Avatar of Marcus Tisäter
Marcus Tisäter

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费试用额度!

以下是 Marcus Tisäter 的客座文章。我认为我们许多人发现 PostCSS 非常吸引人。特别是从用 JavaScript 编写我们自己的插件的角度来看,这些插件可以以我们想要的方式转换 CSS。但是从哪里开始呢?如何做好?马库斯解释道。

你可能听说过人们说制作一个 PostCSS 插件很容易。但是你如何开始呢?别担心,这就是我们将在本文中介绍的内容。然后,我们会向你展示在完成插件后该做什么,以及如何成为一个负责任的 PostCSS 插件开发人员。

最佳实践,我们来了。

我如何开始?

只需几行 JavaScript 代码,就可以在 PostCSS 中实现一些神奇的效果。最好的方法是使用官方的 PostCSS-plugin-boilerplate。这个入门工具包包含一个初始设置脚本、一些 npm 依赖项,以及你的 PostCSS 插件的样板代码;它还提供了一个文件,供你在 AVA(一个测试运行器)中编写测试,并提供了一些文档来帮助你入门。

下面的代码片段是理解插件中最关键的部分。它是一个包装函数,在你的方法中,该方法连接到插件 API 中。

// You have to require the shell itself, PostCSS. 
var postcss = require('postcss');

module.exports = postcss.plugin('pluginname', function (opts) {

  opts = opts || {};

  // Work with options here

  return function (css, result) {
    // Transform the CSS AST
  };

});

首先,我们需要理解 PostCSS 默认情况下为我们提供了什么。PostCSS 本身不执行任何操作;它只是你的插件周围的包装器。PostCSS 核心包含一个解析器,它生成一个 CSS AST(抽象语法树),它是一个表示解析 CSS 字符串的节点树的树。当我们在 CSS 抽象语法树中更改某些内容时,PostCSS 仍然将其表示为根节点,但将语法树重新字符串化回 CSS 字符串。

让我们编写一个非常简单的 PostCSS 插件。我们可以将其称为 PostCSS-backwards。PostCSS-backwards 允许你将 CSS 声明值反转。color: dlog 将反转为 color: gold,反之亦然。

var postcss = require('postcss');

module.exports = postcss.plugin('postcss-backwards', function (opts) {
  opts = opts || {};
  return function (css, result) {

    // Runs through all of the nodes (declorations) in the file
    css.walkDecls(declaration => { 
      declaration.value = declaration.value.split('').reverse().join(''); 
    });

  };
});

当然,这只是一个愚蠢的例子。它没有用,而且没有人会使用它。更重要的是,这对 PostCSS 插件生态系统不利。我们稍后会详细讨论。

在我们深入实际的 PostCSS API 之前,我想让你看一下我最喜欢的 PostCSS 插件之一,PostCSS Focus。PostCSS Focus 在你的样式表中为每个 :hover 添加一个 :focus 选择器。这对于提高可访问性非常有用,因为 :focus 样式经常被遗忘,并且几乎总是应该与 :hover 样式配对。

看一下用一些注释标记的代码,了解这个插件是如何制作的。

var postcss = require('postcss');

module.exports = postcss.plugin('postcss-focus', function () {
  return function (css) {

    // Callback for each rule node.
    css.walkRules(function (rule) { 

      // Match the individual rule selector 
      if ( rule.selector.indexOf(':hover') !== -1 ) { 

         // Array to contain the rule’s individual selector.
        var focuses = []; 
        rule.selectors.forEach(function (selector) { 

           // Passes all declaration values within the match of hover replacing those values with the returned result of focus.
          if ( selector.indexOf(':hover') !== -1 ) {

            focuses.push(selector.replace(/:hover/g, ':focus')); 
          }

        });

        // Checks if array contain values
        if ( focuses.length ) { 
          // Concat the original rules with the new duplicated :focus rules 
          // Groups of selectors are automatically split with commas.
          rule.selectors = rule.selectors.concat(focuses);
        }
      }
    });
  };
});

了解 PostCSS API

我用过的 PostCSS API 是速度最快、最准确的 CSS 解析器。它的文档写得很好,涵盖了你需要掌握它所需要的一切。API 提供了各种数量的函数、类、常用方法和模块供你使用。有一些方法你可能已经熟悉了,比如,prependappendclonecloneAfterreplaceWith。你可以将其与 DOM(文档对象模型)API 相比较,它能够操作 CSS。其理念是相同的。

如果你无法直接使用 PostCSS API 完成你的目标,你可以用所谓的辅助工具扩展你的插件。这些工具包括选择器、值和维度解析器,或者函数和属性解析器。 函数解析器 是其中比较流行的一种。它是一个用于公开 JavaScript 函数的工具。

其他可扩展的工具

什么能让我成为一个负责任的 PostCSS 插件开发者?

做一件事,并做好。

PostCSS 插件应该被创建来做一件特定的事情;它可以像简单地将 :focus 选择器添加到样式表中的每个 :hover,或者将像像素这样的单位大小转换为 em。重要的是要避免编写一个多功能插件,它能做很多事情。PostCSS 用户喜欢选择他们独特的插件集来完成他们需要做的事情。

如果你想构建一个多功能插件,你可以将一组独立的 PostCSS 插件打包成一个包。你可能听说过这样制作的插件,比如 cssnext 用于未来的 CSS 语法,或者 cssnano 用于现代 CSS 压缩。这通常受到 PostCSS 用户的赞赏。它可以帮助他们避免手动挑选自己的集合。

保持简单,笨拙

古老的美国海军设计原则 KISS(保持简单,笨拙)是制作 PostCSS 插件时遵循的完美原则。以 PostCSS-focus 为例,它是一个非常简单的想法,但仍然是一个非常实用的 PostCSS 插件,可以纠正可访问性问题。Unix 哲学在代码方面也完全符合这一点

构建简短、简单、清晰和模块化的代码。

我有一个很棒的 PostCSS 插件想法

PostCSS 有超过 200 个注册的插件,因此在开始为你的插件想法编写代码之前,请在 PostCSS 插件 注册 中查找。很可能它已经被其他人创建了,你并不一定需要重新构建它。相反,互相帮助是构建良好的插件生态系统的关键。我认识的所有 PostCSS 插件作者都非常感谢支持、想法和 pull 请求。

PostCSS 最近开设了一个 建议箱,供用户提交 PostCSS 插件的想法。

跳出框框思考

PostCSS 的神奇之处在于你可以构建一些非常简单但又高效、独特和具有未来感的工具。像 AutoprefixeruncssCSS ModulesStylelint 这样的出色流行工具,都是由 PostCSS 提供支持的。它们是跳出框框思考的绝佳例子。

什么 PostCSS 插件想法不好?

我建议你不要编写“语法糖”插件。

例如,针对非标准声明属性或值的简写前缀,例如将font: 10px 700转换为font-size: 10px; font-weight: 700的插件。它看起来像标准 CSS,但实际上并非如此。想象一下,将使用这种方法的 CSS 代码库交给另一个开发人员。这会导致混乱和沮丧,维护起来更困难,而且学习曲线会不必要地变长。

我认为这就是一些开发者对 PostCSS 仍然持怀疑态度的原因,它让我们能够创造这些可能性。尽管如此,这些语法糖插件还是可能变得非常流行。我们已经看到这些 mixin 库的走红。创建这些的更合适方法是要求以另一种方式声明属性。例如,_font,或者其他不会以不兼容的方式覆盖原生 CSS,而且不太可能成为原生 CSS 的一部分的名称。

利用 PostCSS 的可能性创造新的东西,并保持 CSS 的精神。

– Andrey Sitnik,PostCSS 的创建者

JavaScript 很可怕?!

许多开发者(像我一样)仍然不太习惯使用 JavaScript。我们不认为自己有足够的经验去深入研究 PostCSS 插件的开发。PostCSS 实际上改变了我的想法。看到用几行代码就能完成多少事情,以及开发一个可用的 PostCSS 插件有多容易,真是难以置信。

使用极少代码的 PostCSS 插件

但仍然可以创建独特而有用的东西。来看看吧

让我们谈谈最佳实践

您的 PostCSS 插件应该只迭代一次 CSS AST(抽象语法树),并保留规则缓存,而不是多次迭代步骤。使用节点的字符串表示非常昂贵,所以只在需要时才这样做,最重要的是确保 PostCSS 插件使用TDD(测试驱动开发)开发。

PostCSS API 中有一个方法需要您注意。那就是 node.raws 方法。node.raws 包含生成字节级节点字符串的信息,这可能不好,因为它存储了来自样式表的空格和代码格式化数据。由于每个解析器都可能保存不同的数据,这将变得非常昂贵。

  • 尽可能使用异步方法
  • 为新节点设置node.source(生成准确的源映射)
  • 在 CSS 相关错误上使用node.error(创建源位置)
  • 使用result.warn来进行警告(不要输出console.log()

如果您对此不理解,也不要担心。到时候,请查看官方的插件指南页面以获取更多详细信息。

源映射

PostCSS 具有出色的源映射支持和集成。它可以自动检测您期望的格式,读取和输出内联和外部映射。作为插件开发者,您必须小心,不要破坏源映射功能。

默认情况下,每个节点属性都设置了node.source(包含解析输入中的原始位置)。PostCSS stringify 会计算该节点的新位置,并使用node.source在新的和旧的位置之间创建映射。常见错误是 PostCSS 插件作者忘记在节点创建时添加source,这会导致插件无法映射。对于插件作者来说,最好的也是唯一的建议是使用PostCSS API clone()cloneBefore()cloneAfter(),因为它会复制原始节点的 source 或手动设置 source。

完成后该怎么办?

每个人都可以自由编写 PostCSS 插件并在GitHubnpm上发布它。最终,这一切都归结于对您发布的成果负责。

能力越大,责任越大。

但在我们急于行动之前,PostCSS 插件作者需要遵循一些强制性规则。您可以在这里找到所有规则。让我们带您了解一些关键规则。

TDD(测试驱动开发)

在编写代码时,编写测试也很重要。我认为开发者应该了解测试的目的,并且能够编写测试。从多个方面来说,您应该编写测试的原因有很多。最终输出的质量往往会更好,因为在构建和运行测试时,您会得到很多“顿悟时刻”。从我的个人经验来看,我的代码随着我学习如何从测试失败中重构而得到大幅改进。

我建议使用AVA编写测试,因为它具有非常简单的语法,并且也是 PostCSS 插件中最常用的测试框架。这有助于为您的插件获得最多的贡献者。AVA还支持promise,这对于异步插件和其他好处(例如速度、没有隐式全局变量、ES2015 支持,以及它会同时运行所有测试)非常有用。如果您不喜欢使用AVA,您可以考虑使用ChaiMocha

让我们用AVA为我们的 PostCSS-backwards 插件编写一个简单的测试。

// Function helper to make our tests cleaner
// This runs our plugin
function run(t, input, output, opts = {}){
  return postcss([ plugin(opts) ]).process(input)
    .then( result => {
      t.same(result.css, output);
      t.same(result.warnings().length, 0);
  });
}

// This test passed.
test('reverse color value from dlog to gold', t => {
  return run(t, 'a{ color: dlog }', 'a{ color: gold }');
});

CI 服务

建议使用像Travis这样的 CI 服务在不同的节点环境中测试代码,因为您可以从节点中测试您的代码是否满足要求。我也喜欢 Travis,因为它可配置,并且您可以在提交到您的仓库时自动运行所有测试,并获得通过/失败通知。试试吧!

为插件命名

您的 PostCSS 插件名称应该以“postcss-”为前缀,表明它确实是一个 PostCSS 插件,并且通过阅读名称就可以清楚地了解其功能。最好将名称小写。

  • 首选:postcss-my-cool-plugin
  • 不推荐:PostCSS-My-Cool_Plugin

文档

您不一定需要编写大量文档,但重要的是在 README 中描述您的 PostCSS 插件的作用,并通过显示输入和输出代码示例来说明。这样用户可以更清楚地了解插件的功能。

保持更新日志

为您的 PostCSS 插件发布的每个版本都保持更新的变更日志文件非常重要。这对 *您*(回顾历史)和您的插件的 *用户*(更新和更改的参考)都非常有益。我建议使用像 npmpub 这样的 npm 发布者,并阅读更多关于 keepachangelog.com/ 的信息,了解维护良好的变更日志文件的提示和技巧。

最后的智慧之言

我认为学习如何制作 PostCSS 插件的最佳方法是让自己参与进来,花时间深入了解 PostCSS API 并在 AST Explorer 中进行尝试。

记住,在这一旅程中,您并不孤单;您有 PostCSS 社区来帮助您。PostCSS 最棒的一点就是它的社区。这听起来很老套,但我想要强调的是,您经常可以与 PostCSS 核心背后的专家 交谈 并获得大量帮助。

如果您有任何问题,请随时在评论区留言或 给我发推,我会尽力为您解答。请务必查看官方网站 postcss.org 上的文档。

我要感谢 Ben Briggs、Andrey Sitnik、Dan Gamble、Chris Coyier 和 Malin Gernandt 通过校对和提供想法让这篇文章成为可能。