写点什么

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

评论

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

Wireshark数据包分析学习笔记Day7

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

四、MongoDB查询(2)

Kylin

读书笔记 分布式数据库mongodb 3月日更

关于写东西的一点思考

道伟

28天写作

FutureTask源码解析

程序员星星toC

多线程 Future future设计模式

Elasticsearch Reindex & Index Alias

escray

elastic 28天写作 死磕Elasticsearch 60天通过Elastic认证考试

ARTS - Week 6

Khirye

Java LeetCode arts

软考备考视频的目录

IT蜗壳-Tango

3月日更 软考

【笔记】第七周 第1课

Geek_娴子

引爆40亿播放的抖音春节道具,背后是怎样的技术?

字节跳动技术团队

为什么选择python

张鹤羽

28天写作 3月日更

MySQL原理

Sakura

28天写作 3月日更

习惯

lenka

3月日更

Python yaml 使用的包

HoneyMoose

深读golang中map后思考和借鉴

ninetyhe

源码 Go 语言

华为不养猪,小米没造车,“巨头错觉”是怎么来的?

脑极体

Python 变量类型

依旧廖凯

28天写作 3月日更

Python 通过命令行安装包的时候 pip 提示错误

HoneyMoose

2021最新快手面经主动分享:Java面试神技/技术知识集合(10个专题详细介绍)

比伯

Java 编程 架构 面试 程序人生

Python 基础语法

依旧廖凯

28天写作 3月日更

如果写文字只是自我表达「Day 19」

道伟

28天写作

已经整整10年了,经济学人分析日本福岛核泄漏事故带来的沉重影响

wbliu85

一名优秀的女程序员是如何炼成的?我们跟爱奇艺的五位工程师姐姐聊了聊

爱奇艺技术产品团队

Hello World!!!

SBB

LeetCode题解:125. 验证回文串,双指针,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

JAVA中的I/O模型-多路复用

Java 架构 计算机

甚至你可以在网抑云上听歌

ES_her0

28天写作 3月日更

算法攻关-从上到下打印二叉树(O(n))_offer32

小诚信驿站

架构师 刘晓成 小诚信驿站 28天写作 算法攻关

Python 注释

HoneyMoose

如何带团队?

石云升

程序员 28天写作 职场经验 管理经验 3月日更

价值感知:如何评价企业IT项目的价值?

boshi

价值传递 七日更 项目经验

翻译:《实用的Python编程》04_04_Defining_exceptions

codists

Python

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