写点什么

如何编写属于自己的 PostCSS 8 插件?

  • 2021-10-27
  • 本文字数:4476 字

    阅读完需:约 15 分钟

如何编写属于自己的PostCSS 8插件?

笔者近期在将前端架构 webpack 升级到 5 时,一些配套模块也需要进行升级,其中包括了 css 处理模块 PostCSS。旧版本使用的是 PostCSS 7,在升级至 PostCSS 8 的过程中,笔者发现部分插件前置依赖还是停留在 7 版本,且年久失修,在 PostCSS 8 中出现各种各样的问题,无奈只能研究源码,将目前部分旧版本插件升级至新版本。这里,笔者将升级插件的过程进行简化和提炼,让读者自己也可以编写一个 PostCSS 8 插件。

插件工作原理


PostCSS 是一个允许使用 JS 插件转换样式的工具。开发者可以根据自己的实际需求,在编译过程将指定 css 样式进行转换和处理。目前 PostCSS 官方收录插件有 200 多款,其中包括使用最广泛的Autoprefixer自动补全 css 前缀插件。


PostCSS 和插件的工作原理其实很简单,就是先将 css 源码转换为 AST,插件基于转换后 AST 的信息进行个性化处理,最后 PostCSS 再将处理后的 AST 信息转换为 css 源码,完成 css 样式转换,其流程可以归结为下图:



下面我们通过实际例子看看 PostCSS 会将 css 源码转换成的 AST 格式:


const postcss = require('postcss')postcss().process(`.demo {  font-size: 14px; /*this is a comment*/}`).then(result => {  console.log(result)})
复制代码


代码中直接引用 PostCSS,在不经过任何插件的情况下将 css 源码进行转换,AST 转换结果如下:


{  "processor": {    "version": "8.3.6",    "plugins": []  },  "messages": [],  "root": {    "raws": {      "semicolon": false,      "after": "\n"    },    "type": "root",    // ↓ nodes字段内容重点关注    "nodes": [      {        "raws": {          "before": "\n",          "between": " ",          "semicolon": true,          "after": "\n"        },        "type": "rule",        "nodes": [          {            "raws": {              "before": "\n  ",              "between": ": "            },            "type": "decl",            "source": {              "inputId": 0,              "start": {                "offset": 11,                "line": 3,                "column": 3              },              "end": {                "offset": 26,                "line": 3,                "column": 18              }            },            "prop": "font-size", // css属性和值            "value": "14px"          },          {            "raws": {              "before": " ",              "left": "",              "right": ""            },            "type": "comment", // 注释类            "source": {              "inputId": 0,              "start": {                "offset": 28,                "line": 3,                "column": 20              },              "end": {                "offset": 48,                "line": 3,                "column": 40              }            },            "text": "this is a comment"          }        ],        "source": {          "inputId": 0,          "start": {            "offset": 1,            "line": 2,            "column": 1          },          "end": {            "offset": 28,            "line": 4,            "column": 1          }        },        "selector": ".demo", // 类名        "lastEach": 1,        "indexes": {}      }    ],    "source": {      "inputId": 0,      "start": {        "offset": 0,        "line": 1,        "column": 1      }    },    "lastEach": 1,    "indexes": {},    "inputs": [      {        "hasBOM": false,        "css": "\n.demo {\n  font-size: 14px;\n}\n",        "id": "<input css vi1Oew>"      }    ]  },  "opts": {},  "css": "\n.demo {\n  font-size: 14px;\n}\n"}
复制代码


AST 对象中 nodes 字段里的内容尤为重要,其中存储了 css 源码的关键字、注释、源码的起始、结束位置以及 css 的属性和属性值,类名使用selector存储,每个类下又存储一个 nodes 数组,该数组下存放的就是该类的属性(prop)和属性值(value)。那么插件就可以基于 AST 字段对 css 属性进行修改,从而实现 css 的转换。

PostCSS 插件格式规范及 API


PostCSS 插件其实就是一个 JS 对象,其基本形式和解析如下:


module.exports = (opts = { }) => {  // 此处可对插件配置opts进行处理  return {    postcssPlugin: 'postcss-test', // 插件名字,以postcss-开头        Once (root, postcss) {      // 此处root即为转换后的AST,此方法转换一次css将调用一次    },        Declaration (decl, postcss) {      // postcss遍历css样式时调用,在这里可以快速获得type为decl的节点(请参考第二节的AST对象)    },        Declaration: {      color(decl, postcss) {        // 可以进一步获得decl节点指定的属性值,这里是获得属性为color的值      }    },        Comment (comment, postcss) {        // 可以快速访问AST注释节点(type为comment)    },        AtRule(atRule, postcss) {        // 可以快速访问css如@media,@import等@定义的节点(type为atRule)    }      }}module.exports.postcss = true
复制代码


更多的 PostCSS 插件 API 可以详细参考官方postcss8文档,基本原理就是 PostCSS 会遍历每一个 css 样式属性值、注释等节点,之后开发者就可以针对个性需求对节点进行处理即可。

实际开发一个 PostCSS 8 插件


了解了 PostCSS 插件的格式和 API,我们将根据实际需求来开发一个简易的插件,有如下 css:


.demo {  font-size: 14px; /*this is a comment*/  color: #ffffff;}
复制代码


需求如下:


  1. 删除 css 内注释

  2. 将所有颜色为十六进制的#ffffff转为 css 内置的颜色变量white


根据第三节的插件格式,本次开发只需使用CommentDeclaration接口即可:


// plugin.jsmodule.exports = (opts = { }) => {
return { postcssPlugin: 'postcss-test', Declaration (decl, postcss) { if (decl.value === '#ffffff') { decl.value = 'white' } }, Comment(comment) { comment.text = '' } }}module.exports.postcss = true
复制代码


在 PostCSS 中使用该插件:


// index.jsconst plugin = require('./plugin.js')
postcss([plugin]).process(`.demo { font-size: 14px; /*this is a comment*/ color: #ffffff;}`).then(result => { console.log(result.css)})
复制代码


运行结果如下:


.demo {  font-size: 14px; /**/  color: white;}
复制代码


可以看到,字体颜色值已经成功做了转换,注释内容已经删掉,但注释标识符还依旧存在,这是因为注释节点是包含/**/内容存在的,只要 AST 里注释节点还存在,最后 PostCSS 还原 AST 时还是会把这段内容还原,要做到彻底删掉注释,需要对 AST 的 nodes 字段进行遍历,将 type 为 comment 的节点进行删除,插件源码修改如下:


// plugin.jsmodule.exports = (opts = { }) => {
// Work with options here // https://postcss.org/api/#plugin
return { postcssPlugin: 'postcss-test', Once (root, postcss) { // Transform CSS AST here root.nodes.forEach(node => { if (node.type === 'rule') { node.nodes.forEach((n, i) => { if (n.type === 'comment') { node.nodes.splice(i, 1) } }) } }) },
Declaration (decl, postcss) { // The faster way to find Declaration node if (decl.value === '#ffffff') { decl.value = 'white' } } }}module.exports.postcss = true
复制代码


重新执行 PostCSS,结果如下,符合预期。


.demo {  font-size: 14px;  color: white;}
复制代码

插件开发注意事项


通过实操开发可以看到,开发一个 PostCSS 插件其实很简单,但在实际的插件开发中,开发者需要注意以下事项:

1.尽量使插件简单,使用者可以到手即用


Build code that is short, simple, clear, and modular.


尽量使你的插件和使用者代码解耦,开放有限的 API,同时开发者在使用你的插件时从名字就可以知道插件的功能。这里推荐一个简单而优雅的 PostCSS 插件postcss-focus,读者可以从这个插件的源码中体会这个设计理念。

2.开发插件前确认是否有现成的轮子


如果你对自己的项目有个新点子,想自己开发一个插件去实现,在开始写代码前,可以先到 PostCSS 官方注册的插件列表中查看是否有符合自己需求的插件,避免重复造轮子。不过截止目前(2021.8),大部分插件依旧停留在 PostCSS 8 以下,虽然 PostCSS 8 已经对旧版本插件做了处理,但在 AST 的解析处理上还是有差异,从实际使用过程中我就发现 PostCss8 使用低版本插件会导致 AST 内的source map丢失,因此目前而言完全兼容 PostCSS 8 的插件还需各位开发者去升级。

从低版本 PostCSS 迁移


升级你的 PostCSS 插件具体可以参考官方给出的升级指引。这里只对部分关键部分做下解释:

1.升级 API


  • 将旧版module.exports = postcss.plugin(name, creator)替换为module.exports = creator

  • 新版插件将直接返回一个对象,对象内包含Once方法回调;

  • 将原插件逻辑代码转移至Once方法内;

  • 插件源码最后加上module.exports.postcss = true


具体示例如下。


旧版插件:


- module.exports = postcss.plugin('postcss-dark-theme-class', (opts = {}) => {-   checkOpts(opts)-   return (root, result) => {      root.walkAtRules(atrule => { … })-   }- })
复制代码


升级后插件:


+ module.exports = (opts = {}) => {+   checkOpts(opts)+   return {+     postcssPlugin: 'postcss-dark-theme-class',+     Once (root, { result }) {        root.walkAtRules(atrule => { … })+     }+   }+ }+ module.exports.postcss = true
复制代码

2.提取逻辑代码至新版 API


把逻辑代码都放在Once回调内还不够优雅,PostCSS 8 已经实现了单个 css 的代码扫描,提供了Declaration(), Rule(), AtRule(), Comment() 等方法,旧版插件类似root.walkAtRules的方法就可以分别进行重构,插件效率也会得到提升:


  module.exports = {    postcssPlugin: 'postcss-dark-theme-class',-   Once (root) {-     root.walkAtRules(atRule => {-       // Slow-     })-   }+   AtRule (atRule) {+     // Faster+   }  }  module.exports.postcss = true
复制代码

总结


通过本文的介绍,读者可以了解 PostCSS 8 工作的基本原理,根据具体需求快速开发一个 PostCSS 8 插件,并在最后引用官方示例中介绍了如何快速升级旧版 PostCSS 插件。目前 PostCSS 8 还有大量还没进行升级兼容的 PostCSS 插件,希望读者可以在阅读本文后可以获得启发,对 PostCSS 8 的插件生态做出贡献。

2021-10-27 18:113248

评论

发布
暂无评论
发现更多内容

架构师训练营 1 期 - 第四周作业(vaik)

行之

极客大学架构师训练营

架构师训练营 1 期 - 第四周总结(vaik)

行之

极客大学架构师训练营

架構師訓練營 week4 作業

ilake

浅析 Java 内存模型 三

朱华

Java volatile JMM

架构师训练营第4周作业

悠哉

LeetCode题解:144. 二叉树的前序遍历,使用栈,JavaScript,详细注释

Lee Chen

大前端 LeetCode

浅析 Java 内存模型 一

朱华

Java JMM

第三周架构师训练营作业

Geek_4c1353

极客大学架构师训练营

spring-boot-route(十一)数据库配置信息加密

Java旅途

Java Spring Boot

第 4 周 系统架构总结

bearlu

系统架构

Redis-技术专题-哨兵配置和原理

洛神灬殇

详解「区块链」溯源

netkiller

区块链 防伪 超级账本 标签 区块链产品溯源

架構師訓練營 week4 總結

ilake

一个典型的大型互联网应用系统使用了哪些技术方案和手段,主要解决什么问题?

knight

《我想进大厂》之Redis夺命连环11问

艾小仙

Java redis 面试 程序语言

go-zero流数据处理利器

万俊峰Kevin

stream functional Go 语言

第四周作业

Geek_4c1353

极客大学架构师训练营

数字人民币真的来了 六年历程全回顾

CECBC

数字货币 DCEP

架构师训练营第 1 期 -week4

习习

甲方日常 27

句子

生活 工作 随笔杂谈 日常

spring-boot-route(十二)整合redis做为缓存

Java旅途

Java redis Spring Boot

第三周课后练习

大大猫

极客大学架构师训练营

典型互联网公司使用的技术

happy

架构师训练营 - 第四周作业

一个节点

极客大学架构师训练营

架构师训练营 - 第四周总结

一个节点

极客大学架构师训练营

CPU 执行程序的秘密,藏在了这 15 张图里

小林coding

操作系统 计算机基础 计算机 编译器、程序语言、CPU 指令

第二周课后练习

大大猫

极客大学架构师训练营

Spring学习笔记(一)手写一个简单的Spring

无语

Spring Framework

第4周 作业一

bearlu

第9周作业

Vincent

极客时间 极客大学

第9周总结

Vincent

极客时间 极客大学

如何编写属于自己的PostCSS 8插件?_大前端_李佳浩_InfoQ精选文章