《HarmonyOS:领航者说》技术公开课来啦,大咖分享、实战解码,不容错过 了解详情
写点什么

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

评论

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

行云管家荣登36kr企服点评云计算软件排行榜NO.1

行云管家

云计算 软件 排行榜 IT运维

真香!180页100+题15W+字解析的《Java高级面试指南》,果断收下

Java 程序员 架构 分布式 算法

墨天轮国产数据库沙龙 | 黄新著:金仓数据库全生命周期管控

墨天轮

国产数据库 KingBase 人大金仓

低代码是什么意思?

低代码小观

程序员 低代码 开发工具 开发平台 企业开发系统

技术干货|开源项目-FlyFish使用攻略

云智慧AIOps社区

开源 大前端 低代码 数据可视化 大屏

百度人脸活体检测系统通过信通院“护脸计划”首批优秀级安全防护能力评估

百度开发者中心

安全 人脸识别 百度安全

你以为委派模式很神秘,其实你每天都在用

Tom弹架构

Java 架构 设计模式

业务数据清洗,落地实现方案

数据 数据清洗 数据管理 数据服务 业务数据

为什么那么多人在用WGCLOUD

王逅逅

zabbix 监控系统 linux运维 运维系统

恒源云(GPUSHARE)_Child Tuning: 反向传播版的Dropout

恒源云

深度学习

内在可解释模型之RuleFit

索信达控股

机器学习 算法 模型

入职字节跳动那一天,我哭了(蘑菇街被裁,奋战7个月拿下offer)

Java MySQL redis 程序员 算法

腾讯安全李滨:腾讯云数据安全与隐私保护探索与实践

腾讯安全云鼎实验室

数据安全 云安全

Apache APISIX 扩展指南

API7.ai 技术团队

Apache 插件 API网关 Apache APISIX

白码低代码/无代码开发平台功能及作用

低代码小观

低代码 开发工具 开发平台 无代码 企业服务

个人信息保护法生效,企业数据安全合规正当时

行云管家

信息安全 数据安全 企业安全 网络保护

《Linux一学就会》:第二章:Linux基本命令操作和文件管理

侠盗安全

Linux 运维 linux运维 云计算架构师

11.11上云嘉年华,华为云数据库助力客户备战业务高峰

华为云数据库小助手

GaussDB GaussDB(for openGauss) GaussDB ( for Redis ) 华为云数据库

原来我才是内卷王,闭关3个月肝完Java 7大核心知识,成功斩获字节58万Offer。

Java高级开发

字节跳动 java; 字节跳动面经

推动产业创新,腾讯的底层逻辑是什么?

ToB行业头条

IOS技术分享| WebRTC iOS源码下载&编译

anyRTC开发者

ios 音视频 WebRTC 实时通信 视频直播

JavaIO流核心模块与基本原理

Java nio IO流 字符流 字节流

springboot集成阿里云短信

小鲍侃java

11月日更

初识Java反射概念和使用

CRMEB

让脂肪起内讧?从内部全面瓦解脂肪

脑极体

极光笔记丨Spark SQL 在极光的建设实践

极光GPTBots-极光推送

大数据 spark 计算引擎

OceanBase 源码解读(六):存储引擎详解

OceanBase 数据库

数据库 开发者 高性能 资源隔离 租户

一文,动态规划入门

bigsai

算法 动态规划

什么是DISA STIG?概述+STIG安全

旋极智能

“神算子”上线!EasyDL时序预测模型零门槛轻松上手

百度开发者中心

百度飞桨

如何获取所有安装的应用程序信息

Changing Lin

11月日更

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