整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

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

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

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

// plugin.js
module.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.js
const 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.js
module.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 的插件生态做出贡献。

nicode 联盟(Unicode Consortium)

Unicode 联盟(Unicode Consortium)开发了 Unicode 标准(Unicode Standard)。他们的目标是使用标准的 Unicode 转换格式(即 UTF,全称 Unicode Transformation Format)取代现有的字符集。

Unicode 标准是一个成功的创举,在 HTML、XML、Java、JavaScript、E-mail、ASP、PHP 中都得到实现。Unicode 标准也得到许多操作系统和所有现代浏览器的支持。

Unicode 联盟与领先的标准开发组织合作,这些组织有 ISO、W3C 和 ECMA。


Unicode 字符集

Unicode 可以由不同的字符集实现。最常用的编码是 UTF-8 和 UTF-16:

字符集描述
UTF-8UTF8 中的字符可以是 1 到 4 字节长。UTF-8 可以代表 Unicode 标准中的任何字符。UTF-8 向后兼容 ASCII。UTF-8 是电子邮件和网页的首选编码。
UTF-1616 位 Unicode 转换格式是一种可变长度的 Unicode 字符编码,能够编码整个 Unicode 指令表。UTF-16 主要用于操作系统和环境,如 Microsoft Windows、Java 和 .NET。

提示:Unicode 的前 128 个字符(与 ASCII 一一对应)使用一个与 ASCII二进制值相同的八位组进行编码,使有效的 ASCII 文本在进行 UTF-8 编码时也是有效的。

提示:所有的 HTML 4 处理器支持 UTF-8,所有的 HTML 5 和 XML 处理器支持 UTF-8 和 UTF-16!


HTML5 标准:Unicode UTF-8

因为 ISO-8859 中字符集大小是有限的,且在多语言环境中不兼容,所以 Unicode 联盟开发了 Unicode 标准。

Unicode 标准覆盖了(几乎)所有的字符、标点符号和符号。

Unicode 使文本的处理、存储和运输,独立于平台和语言。

HTML-5 中默认的字符编码是 UTF-8。

下面列出了一些 HTML5 支持的 UTF-8 字符集:

字符集十进制十六进制
C0 控制与基本的 Latin(C0 Controls and Basic Latin)0-1270000-007F
C1 控制与 Latin-1 的补充(C1 Controls and Latin-1 Supplement)128-2550080-00FF
Latin 扩展 A(Latin Extended-A)256-3830100-017F
Latin 扩展 B(Latin Extended-B)384-5910180-024F

如果 HTML5 网页使用不同于 UTF-8 的字符,则需要在 <meta> 标签中指定,如下:

实例

<meta charset="ISO-8859-1">

如您还有不明白的可以在下面与我留言或是与我探讨QQ群308855039,我们一起飞!

ello大家好,今天广州蓝景跟大家分享一些html的使用技巧。

1. 使用capture属性打开设备摄像头

正如input标签具有email、text和password属性一样,我们也可以通过一些属性打开移动设备的摄像头以捕获图像。

那就是capture属性,属性值有两个:

  • user用于前置摄像头
  • environment用于后置摄像头
<input type="file" capture="user" accept="image/*">

2. 网站自动刷新

你可以在head标签中将网站设置为定时刷新!

<head>
    <meta http-equiv="refresh" content="10">
</head>

此代码段可以实现每10秒刷新一次网站。

3. 激活拼写检查

你可以使用HTML的spellcheck属性并将其设置为true以激活拼写检查。使用lang属性指定待检查的语言。

<input type="text" spellcheck="true" lang="en">

这是一个标准属性,得到了大多数浏览器的支持。

4. 指定要上传的文件类型

你可以使用accept属性在input标签中指定允许用户上传的文件类型。

<input type="file" accept=".jpeg,.png">

5. 阻止浏览器翻译

将translate属性设置为no会阻止浏览器翻译该内容。如果你不想翻译某个短语或单词,例如logo、公司或品牌名称,那就可以应用这个属性。

<p translate="no">Brand name</p>

6. 在input标签中输入多个项目

这可以通过multiple属性来完成。

<input type="file" multiple>

适用于文件和电子邮件。如果是电子邮件,则可以用逗号分隔。

7. 为视频创建海报(缩略图)

使用poster属性,我们可以在视频加载时,或者在用户点击播放按钮之前,显示指定的缩略图。

如果不指定图片,则默认使用视频的第一帧作为缩略图。

<video poster="picture.png"></video>

8. 点击链接自动下载

如果你希望在单击目标资源的链接时下载特定资源,那就添加download属性。

<a href="image.png" download>

今天就分享到这里,想要了解更多前端技术知识,可以关注我们广州蓝景。