以下是 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 提供了各种数量的函数、类、常用方法和模块供你使用。有一些方法你可能已经熟悉了,比如,prepend
,append
,clone
,cloneAfter
或 replaceWith
。你可以将其与 DOM(文档对象模型)API 相比较,它能够操作 CSS。其理念是相同的。
如果你无法直接使用 PostCSS API 完成你的目标,你可以用所谓的辅助工具扩展你的插件。这些工具包括选择器、值和维度解析器,或者函数和属性解析器。 函数解析器 是其中比较流行的一种。它是一个用于公开 JavaScript 函数的工具。
其他可扩展的工具
- 值解析器 – 将 CSS 声明值和 at 规则参数转换为节点树。
- 属性解析器 – 用于解析规则的属性值的辅助方法
- 选择器解析器 – 选择器解析器,带有用于处理选择器字符串的内置方法。
- 数字、长度和百分比的维度解析器。 – 将 CSS 维度解析为 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 的神奇之处在于你可以构建一些非常简单但又高效、独特和具有未来感的工具。像 Autoprefixer、uncss、CSS Modules 和 Stylelint 这样的出色流行工具,都是由 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-will-change – 在
will-change
属性之前插入 3D hack。 - postcss-at-root – 将规则直接放置在根节点。
- postcss-calc – 在可能的情况下减少
calc()
引用。 - postcss-z-index – 减少
z-index
值。 - postcss-alias – 使用
@alias
规则为 CSS 属性创建自定义别名。 - postcss-easings – 将 easings.net 中的缓动名称替换为
cubic-bezier()
。 - postcss-verthorz – 添加垂直和水平间距简写。
让我们谈谈最佳实践
您的 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 插件并在GitHub和npm上发布它。最终,这一切都归结于对您发布的成果负责。
能力越大,责任越大。
但在我们急于行动之前,PostCSS 插件作者需要遵循一些强制性规则。您可以在这里找到所有规则。让我们带您了解一些关键规则。
TDD(测试驱动开发)
在编写代码时,编写测试也很重要。我认为开发者应该了解测试的目的,并且能够编写测试。从多个方面来说,您应该编写测试的原因有很多。最终输出的质量往往会更好,因为在构建和运行测试时,您会得到很多“顿悟时刻”。从我的个人经验来看,我的代码随着我学习如何从测试失败中重构而得到大幅改进。
我建议使用AVA编写测试,因为它具有非常简单的语法,并且也是 PostCSS 插件中最常用的测试框架。这有助于为您的插件获得最多的贡献者。AVA还支持promise
,这对于异步插件和其他好处(例如速度、没有隐式全局变量、ES2015 支持,以及它会同时运行所有测试)非常有用。如果您不喜欢使用AVA,您可以考虑使用Chai或Mocha。
让我们用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 通过校对和提供想法让这篇文章成为可能。
干得好,Marcus!
好文章。我一直很喜欢 PostCSS 的想法,但从未抽出时间深入研究。说实话,Sass 已经解决了我的问题,而且目前我没有看到 PostCSS 可以解决的任何其他问题。
P.S. 应该是 “syntactic sugar”,而不是 “syntax sugar” :)
是的,在当前项目中,没有理由将 Sass 混合器替换为 PostCSS 混合器。
但并非所有问题都无法通过混合器解决。我认为您应该已经在使用 PostCSS 和 Autoprefixer 来解决前缀问题。
谢谢 Scott。Sass 仍然是一种非常有用的语言,并不一定需要完全被 PostCSS 取代。您可以从添加几个插件开始,比如用于供应商前缀的 autoprefixer 或用于 rem 单位添加像素回退的 pixrem。有大量的插件可以帮助解决问题或节省时间,我相信您一定能找到一些能够说服您添加 PostCSS 的插件,而这些插件在 Sass 中是无法实现的。
很棒的文章 #marcus.. 您说得对,Sass 已经很稳定了,并且有很多有用的工具和功能。所以我们可以使用 PostCSS 来实现一些额外的功能,比如 pixrem、rtlcss、svg 等。
很棒的介绍 Marcus Tisäter。我一直想写一篇,但不知道从哪里开始。问题解决了 :) 再谢了!