编写 PostCSS 插件
链接
文档
支持
- 提问
- PostCSS twitter 了解最新更新。
步骤 1:创建创意
在很多领域,编写新的 PostCSS 插件将有助于你的工作
- 兼容性修复:如果你总是忘记添加浏览器兼容性修补程序,你可以创建 PostCSS 插件,为你自动插入此修补程序。
postcss-flexbugs-fixes
和postcss-100vh-fix
是很好的示例。 - 自动化例程操作:让计算机执行例程操作,让自己有时间进行创造性任务。例如,PostCSS 与 RTLCSS 可以自动将设计转换为从右到左的语言(如阿拉伯语或希伯来语),或与 postcss-dark-theme-class` 可以插入深色/浅色主题切换器的媒体查询。
- 防止常见错误:“如果错误发生两次,它将再次发生。”PostCSS 插件可以检查你的源代码是否存在常见错误,并为你节省不必要的调试时间。执行此操作的最佳方法是 编写新的 Stylelint 插件(Stylelint 在内部使用 PostCSS)。
- 提高代码可维护性:CSS Modules 或
postcss-autoreset
是 PostCSS 如何通过隔离提高代码可维护性的绝佳示例。 - Polyfill:我们已经在
postcss-preset-env
中为 CSS 草案提供了很多 Polyfill。如果你找到新的草案,你可以添加一个新插件并将其发送到此预设。 - 新的 CSS 语法:我们建议避免向 CSS 中添加新语法。如果你想添加新功能,最好始终编写 CSS 草案提案,将其发送到 CSSWG,然后实现 Polyfill。
postcss-easing-gradients
与 此提案 是一个很好的示例。但是,在很多情况下,你无法发送提案。例如,浏览器的解析器性能极大地限制了 CSSWG 嵌套语法,你可能希望从 `postcss-nested 获得非官方的类似 Sass 的语法。
步骤 2:创建项目
编写插件有两种方法
- 创建私有插件。仅在插件与项目的具体内容相关时使用此方法。例如,您想为自己的独特 UI 库自动执行特定任务。
- 发布公共插件。这始终是推荐的方法。请记住,即使在 Google 中,私有前端系统也常常得不到维护。另一方面,许多流行的插件是在封闭源项目的工作过程中创建的。
对于私有插件
- 在
postcss/
文件夹中创建一个新文件,其名称为您的插件名称。 - 从我们的样板中复制 插件模板。
对于公共插件
- 使用 PostCSS 插件样板 中的指南来创建插件目录。
- 在 GitHub 或 GitLab 上创建一个存储库。
- 将您的代码发布到那里。
module.exports = (opts = {}) => {
// Plugin creator to check options or prepare caches
return {
postcssPlugin: 'PLUGIN NAME'
// Plugin listeners
}
}
module.exports.postcss = true
步骤 3:查找节点
大多数 PostCSS 插件做两件事
- 在 CSS 中查找内容(例如,
will-change
属性)。 - 更改找到的元素(例如,在
will-change
之前插入transform: translateZ(0)
作为旧浏览器的填充)。
PostCSS 将 CSS 解析为节点树(我们称之为 AST)。此树可能包含
Root
:树顶部的节点,表示 CSS 文件。AtRule
:以@
开头的语句,如@charset "UTF-8"
或@media (screen) {}
。Rule
:内部带有声明的选择器。例如input, button {}
。Declaration
:键值对,如color: black
;Comment
:独立注释。选择器、at-rule 参数和值中的注释存储在节点的raws
属性中。
您可以使用 AST Explorer 来了解 PostCSS 如何将不同的 CSS 转换为 AST。
您可以通过向插件对象添加方法来查找具有特定类型的节点
module.exports = (opts = {}) => {
return {
postcssPlugin: 'PLUGIN NAME',
Once (root) {
// Calls once per file, since every file has single Root
},
Declaration (decl) {
// All declaration nodes
}
}
}
module.exports.postcss = true
以下是 插件事件 的完整列表。
如果您需要具有特定名称的声明或 at-rule,可以使用快速搜索
Declaration: {
color: decl => {
// All `color` declarations
}
'*': decl => {
// All declarations
}
},
AtRule: {
media: atRule => {
// All @media at-rules
}
}
对于其他情况,您可以使用正则表达式或特定解析器
用于分析 AST 的其他工具
别忘了,正则表达式和解析器是繁重的任务。在使用繁重的工具检查节点之前,可以使用 String#includes()
进行快速测试
if (decl.value.includes('gradient(')) {
let value = valueParser(decl.value)
…
}
有两种类型的侦听器:进入和退出。Once
、Root
、AtRule
和 Rule
将在处理子级之前调用。OnceExit
、RootExit
、AtRuleExit
和 RuleExit
在处理节点内的所有子级之后调用。
你可能希望在侦听器之间重复使用一些数据。你可以使用运行时定义的侦听器
module.exports = (opts = {}) => {
return {
postcssPlugin: 'vars-collector',
prepare (result) {
const variables = {}
return {
Declaration (node) {
if (node.variable) {
variables[node.prop] = node.value
}
},
OnceExit () {
console.log(variables)
}
}
}
}
}
你可以使用 prepare()
动态生成侦听器。例如,使用 Browserslist 获取声明属性。
步骤 4:更改节点
当你找到正确的节点时,你需要更改它们或在周围插入/删除其他节点。
PostCSS 节点具有类似 DOM 的 API 来转换 AST。查看我们的 API 文档。节点具有遍历的方法(如 Node#next
或 Node#parent
),查找子级(如 Container#some
),删除节点或在内部添加新节点。
插件的方法将在第二个参数中接收节点创建器
Declaration (node, { Rule }) {
let newRule = new Rule({ selector: 'a', source: node.source })
node.root().append(newRule)
newRule.append(node)
}
如果你添加了新节点,则复制 Node#source
以生成正确的源映射非常重要。
插件将重新访问你更改或添加的所有节点。如果你要更改任何子级,插件也将重新访问父级。只有 Once
和 OnceExit
不会再次调用。
const plugin = () => {
return {
postcssPlugin: 'to-red',
Rule (rule) {
console.log(rule.toString())
},
Declaration (decl) {
console.log(decl.toString())
decl.value = 'red'
}
}
}
plugin.postcss = true
await postcss([plugin]).process('a { color: black }', { from })
// => a { color: black }
// => color: black
// => a { color: red }
// => color: red
由于访问者将在任何更改时重新访问节点,因此仅添加子级将导致无限循环。为了防止这种情况,你需要检查是否已经处理了此节点
Declaration: {
'will-change': decl => {
if (decl.parent.some(decl => decl.prop === 'transform')) {
decl.cloneBefore({ prop: 'transform', value: 'translate3d(0, 0, 0)' })
}
}
}
你还可以使用 Symbol
标记已处理的节点
const processed = Symbol('processed')
const plugin = () => {
return {
postcssPlugin: 'example',
Rule (rule) {
if (!rule[processed]) {
process(rule)
rule[processed] = true
}
}
}
}
plugin.postcss = true
第二个参数还具有 result
对象以添加警告
Declaration: {
bad: (decl, { result }) {
decl.warn(result, 'Deprecated property bad')
}
}
如果你的插件依赖于另一个文件,则可以将消息附加到 result
以向运行器(webpack、Gulp 等)表示当此文件更改时它们应该重建 CSS
AtRule: {
import: (atRule, { result }) {
const importedFile = parseImport(atRule)
result.messages.push({
type: 'dependency',
plugin: 'postcss-import',
file: importedFile,
parent: result.opts.from
})
}
}
如果依赖项是目录,则应使用 dir-dependency
消息类型
result.messages.push({
type: 'dir-dependency',
plugin: 'postcss-import',
dir: importedDir,
parent: result.opts.from
})
如果你发现语法错误(例如,未定义的自定义属性),则可以抛出特殊错误
if (!variables[name]) {
throw decl.error(`Unknown variable ${name}`, { word: name })
}
步骤 5:与挫折作斗争
我讨厌编程
我讨厌编程
我讨厌编程
它起作用了!
我喜欢编程
即使是一个简单的插件,你也会遇到错误,并且至少需要 10 分钟来调试。你可能会发现简单的原始想法在现实世界中行不通,你需要改变一切。
别担心。每个错误都是可以找到的,找到另一个解决方案可能会让你的插件变得更好。
从编写测试开始。插件样板在 index.test.js
中有一个测试模板。调用 npx jest
来测试你的插件。
在你的文本编辑器中使用 Node.js 调试器或 console.log
来调试代码。
PostCSS 社区可以帮助你,因为我们都遇到了相同的问题。不要害怕在 特殊频道 中提问。
步骤 6:公开
当你的插件准备就绪时,在你的存储库中调用 npx clean-publish
。 clean-publish
是一个从 npm 包中删除开发配置的工具。我们已将此工具添加到我们的插件样板中。
用 @postcss
提及来发一条关于你的新插件(即使它是一个小插件)的推文。或者在 [我们的聊天室] 中介绍你的插件。我们会帮助你进行营销。
将你的新插件添加到 PostCSS 插件目录。