在 2025 收官前,看清 Data + AI 的真实走向,点击查看 BUILD 大会精华版 了解详情
写点什么

如何编写属于自己的 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:114008

评论

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

Zabbix agent2 自定义SQL监控和告警实施指南:针对TiDB数据库

TiDB 社区干货传送门

监控 实践案例 管理与运维

新项目如何开展测试工作

老张

项目管理 软件测试 质量保障

分布式系统架构7:本地缓存

卷福同学

Java 分布式 后端

TiDB7.5.5版本加索引巨慢问题梳理

TiDB 社区干货传送门

7.x 实践

CST软件如何仿真GPS上半球空间的辐射占比

思茂信息

cst cst操作 CST软件

Lynx TiDB 慢日志收集工具

TiDB 社区干货传送门

性能调优

nginx适配Overlay以及测试工具

天翼云开发者社区

nginx 虚拟化

Zilliz Cloud上新:容量提升3倍、享5折优惠,支持高精度搜索

Zilliz

zilliz cloud

文档解析技术指南:从传统Pipeline到端到端大模型

Baihai IDP

程序员 AI 文档理解 LLMs

语义检索效果差?深度学习rerank VS 统计rerank选哪个

Zilliz

Milvus 重排 语义搜索 混合搜索

人工智能如何影响社会公平与资源分配?

天津汇柏科技有限公司

AI 人工智能

测试右移的价值与实践体系:打造高效软件测试之路

测试人

软件测试

1 行命令引发的Go应用崩溃

阿里技术

阿里云 命令 排查 Go应用

向量数据库真的能满足所有 AI Agent 的记忆需求吗?

Baihai IDP

程序员 AI LLMs AI Agents

WebGL 技术在 AR 中的应用及其优势

北京木奇移动技术有限公司

软件外包公司 webgl开发 AR应用

WebGL 技术开发 MR 应用的技术难点

北京木奇移动技术有限公司

软件外包公司 webgl开发 MR应用

技术干货丨 OptiStruct 非线性之前车门过开分析(内附模型下载)

Altair RapidMiner

CAE 汽车仿真 仿真设计 车门仿真 非线性仿真

TiDB 的 TiFlash 怎么用 | TiFlash 的最佳场景&稳定性管理

TiDB 社区干货传送门

7.x 实践

以技术创新引领数据要素行业发展,隐语开源社区2024迈上新台阶!

隐语SecretFlow

你知道网络安全相关法律法规都有哪些吗?看这里!

行云管家

网络安全 堡垒机

TiDB 工具 | PD全部扩缩容替换注意事项

TiDB 社区干货传送门

实践案例 集群管理 管理与运维 故障排查/诊断 扩/缩容

WebGL 开发 VR 应用的技术难点

北京木奇移动技术有限公司

VR开发 软件外包公司 webgl开发

2024 TiDB 社区年度总结,又携手共进了一年,2025年,一起迎接变化,挑战变化!

TiDB 社区干货传送门

探究获取亚马逊畅销榜API接口及实战应用

科普小能手

数据挖掘 数据分析 电商 亚马逊 API 接口

7.5.4 MVCC优化测试

TiDB 社区干货传送门

7.x 实践

火语言RPA轻松开发控制台程序或带界面交互的客户端应用

火语言RPA

RPA 自动化 低代码 影刀RPA 火语言

主机防护如何更安全、高效? HSS新增多种特性,让你少走弯路

华为云开发者联盟

华为云 主机安全 云图说 新版本

测试三大难题之一:“测试有效性”的应对策略

测试人

软件测试

泳池机器人Aiper,从价值链高处“游”进全球庭院

脑极体

AI

腾讯一面,感觉问Redis的难度不是很大

王中阳Go

redis 腾讯 面试 面试问题

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