写点什么

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

评论

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

JPA + EclipseLink + 云平台 = 运行在云端的数据库应用

汪子熙

数据库 Cloud Cloud Native 11月日更

教你如何在Spark Scala/Java应用中调用Python脚本

华为云开发者联盟

Java Python spark JVM Spark Scala

TDSQL | 深度解读HTAP系统的问题与主义之争

腾讯云数据库

tdsql 国产数据库

飞鹤乳业数智化转型之路

大咖说

云计算 数字化转型 数字化 企业上云

恒源云(GPUSHARE)_U1S1,1年1度GPU云种草大会

恒源云

深度学习

2020 国内互联网公司的Android工程师薪酬排名!看看你是什么水平

android 程序员 移动开发

2020Android 开发年度总结:“这一年里我到底做了些啥,面试阿里的时候一定会问到的

android 程序员 移动开发

极复杂编码,下载《原神》角色高清图、中日无损配音,爬虫 16 / 120 例

梦想橡皮擦

11月日更

推荐!DevOps工具正越来越自动化

飞算JavaAI开发助手

性能优化反思:不要在for循环中操作DB

CRMEB

实用推荐系统:寻找有用的用户行为

博文视点Broadview

用一套代码实现APP和小程序

Speedoooo

容器 移动开发 ios开发 APP开发 Andriod开发

超详细攻略!手把手教你如何在windows下搭建openLooKeng开发环境

LooK

大数据 计算引擎

基于Hive Connector的openLooKeng Connector 创建复用机制剖析

LooK

大数据 hive 多数据源配置 计算引擎 openLooKeng

新版本发布!openLooKeng v1.4.0上线

LooK

大数据 计算引擎 openLooKeng

TDSQL将发布免费版本,助力国产数据库生态完善

腾讯云数据库

数据库 tdsql

TDSQL已助力20余家金融机构完成核心替换

腾讯云数据库

tdsql 国产数据库

2020-Android-面试重难点(万字篇),android屏幕适配的五种方式

android 程序员 移动开发

2020 年需要关注的 5 大 Android 开发技术(1),Android知识总结

android 程序员 移动开发

模块三作业

doublechun

「架构实战营」

Hazelcast在openLooKeng中的应用(Cache篇)

LooK

大数据 cache 计算引擎 openLooKeng

超简单教程!自动部署openLooKeng

LooK

大数据 计算引擎 openLooKeng 安装部署

浅析openLooKeng安全认证机制

LooK

大数据 ldap openLooKeng 安全认证

使用JPA + Eclipselink操作PostgreSQL数据库

汪子熙

eclipse 数据库 11月日更

【Java原理剖析系列】深度synchronized工作原理分析

码界西柚

java 11月日更

助力邯钢工业4.0!TDengine在深度(平潭)节水减排项目中的应用

TDengine

数据库 tdengine 后端

硬核干货!TDSQL全局一致性读技术详解|

腾讯云数据库

tdsql 国产数据库

2020 更新 - 腾讯 Android 面试 (已拿到月薪22K offer)

android 程序员 移动开发

2020Android-目前最稳定和高效的UI适配方案!你头秃都没想到还能这样吧!

android 程序员 移动开发

云上远程运维的最后那点担心,“云梯”帮你解决

华为云开发者联盟

运维 华为云Stack 远程运维 安全可信 云梯

双11在即,分享一些稳定性保障技术干货

老张

系统稳定性 大促 生产环境全链路压测

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