postcss 运用及原理
postcss 入坑指南
目标:
- 掌握 postcss 的使用
- 自定义 postcss 插件
- 掌握 stylelint 的使用
- 自定义 stylelint rule
- 扩展 css parser 解释器
这篇文章大部分工作都在调试 postcss 源码,涉及到的源码见 https://github.com/gaollard/postcss-demo/blob/master/doc.md
1. postcss 是什么
在聊 postcss 之前,我们需要知道什么是 CSS 后处理工具。我们比较熟悉的 Less/Sass/Stylus,这类工具都属于CSS 预处理工具。预处理指的是通过特殊的规则,将非 css 文本格式最终生成 css 文件,而 postcss 则是对 CSS 进行处理,最终生成CSS。
可能大部分前端开发者都使用过 Autoprefixer 这款插件,它以 Can I Use (浏览器兼容性支持) 为基础,自动处理兼容性问题,下面是一个简单的例子:
// Autoprefixer 处理前的CSS样式
.container {
display: flex;
}
.item {
flex: 1;
}
// Autoprefixer 处理后的CSS样式
.container {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
.item {
-webkit-box-flex: 1;
-webkit-flex: 1;
-ms-flex: 1;
flex: 1;
}
在这个例子中过,通过使用 Autoprefixer 插件,帮助我们自动处理浏览器前缀,极大的提高了编码效率。其实,Autoprefixer 正是 postcss 众多插件中的一款,postcss 提供的简洁明了API,并且文档十分详细,这为其生态建设提供了有力的支撑。
点击 这里 查看更多可用插件。
2. postcss 如何使用
两个主要的功能:
- 转换 css,这是我们最常使用的
- 获取 css ast,当我们编写插件时需要掌握
2.1 转换 css
这里只介绍 postcss API,如果您使用 webpack,只需要将 postcss 包装为 postcss-loader 即可。
// 01-simple-demo
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const precss = require('precss')
const fs = require('fs')
const path = require('path')
const src = path.resolve('./src/app.css');
const dest = path.resolve('./dest/app.css');
fs.readFile(src, (err, css) => {
postcss([precss, autoprefixer]) // [precss, autoprefixer] 为使用的插件列表,返回 Processor 对象
.process(css) // process 接收 css 资源
.then(result => { // result 为 Result 实例
fs.writeFile(dest, result.css, function(err) {
if (err) throw err;
});
})
})
相关源码:
- postcss/lib/postcss.js
- postcss/lib/processor.js
2.2 获取 CSS AST
// 02-use-parser
const postcss = require('postcss')
const fs = require('fs')
const path = require('path')
const src = path.resolve('./src/app.css');
const css = fs.readFileSync(src);
const root = postcss.parse(css);
console.log(root); // CSS 抽象语法树
相关源码:
- postcss/lib/parse.js
- postcss/lib/parser.js
3. postcss 的运行过程
主要步骤:
- 解释:接收输入的css,将css内容处理成css抽象语法树。
- 转换:根据配置插件的顺序对树型结构的 AST 进行操作。
- 输出:最终将处理后获得的 AST S 对象输出为 css 文件。
主要内容:
- 标记器:将 css 拆解为 token 序列,为语法树提供基础(postcss/lib/tokenize.js ...)
- 解释器:通过语法分享,将 token 序列转换为语法树(postcss/lib/parser.js ...)
- 处理器:根据插件配置,对语法树做一些转换操作(postcss/lib/lazy-result.js ...)
最不容易理解的也是最难的点:CSS AST 的生成以及操作。
4. postcss CSS AST
你暂且将 AST 理解一个节点树,这些节点不完全相同,它们继承自同一个节点(源码中为Container)。
在学习 postcss 初期,通过查看可视化的 postcss css 语法树,可以帮助你理解。使用使用 css ast 在线工具,下图为一个很标准的 css 文档,有注释、媒体查询,以及选择器样式:
/**
* Paste or drop some CSS here and explore
* the syntax tree created by chosen parser.
* Enjoy!
*/
@media screen and (min-width: 480px) {
body {
background-color: lightgreen;
}
}
#main {
border: 1px solid black;
}
ul li {
padding: 5px;
}
上面的 css 最终会处理为下图结构,通过打印信息我们可以发现树型结构的 JS 对象是一个名为 Root 的构造函数,而起树型结构的 nodes 节点下还有 Common,AtRule, Rule 构造函数。

CSS AST 节点主要有以下构造类组成:
- Root: 根结点,整个处理过程基本上都在围绕着 Root,Commont,AtRule,Rule 都是它的子节点。
- Commont: css 中的注释信息,注释的内容在 comment.text 下。
- AtRule: 带@标识的部分,name 为标识名称,params 为标识参数。nodes 为内部包含的其他子节点,可以是 Commont,AtRule,Rule,这让我们可以自定义更多的规则。
- Declaration:每个 css 属性以及属性值就代表一个 declaration
4.1 Rule 选择器节点
一个选择器代表一个Rule,选择器对应的样式列表 nodes 为 Declaration构造函数

- raws
- before 距离前一个兄弟节点之间的内容
- between 选择器与 { 之间的内容
- semicolon 最后一个属性是否带分号
- after 最后一个属性 和 } 之间的内容

- type 节点类型
- nodes 子节点
- source
- start 开始位置
- end 结束位置
- selecter 选择器
大部分节点结构是类似的,如果你理解了 Rule 节点的结构,相信其他类型的节点对你也是很轻松的!
4.2 Declaration 属性节点
Declaration 是 css 样式属性,prop为样式属性,value为样式值。可给 Rule 手动添加样式属性,也可以修改prop,value。上文提到的 Autoprefixer 就是通过 clone 当前属性,修改 prop 并添加到选择器下,Declaration 节点非常简单:

4.3 Comment 注释节点

5. 各构造器方法和属性
大部分节点都继承了 Container,因此我们先看看公共属性:
- nodes 子节点
- parent 父节点
- raws 相当于分隔符集合
- source 位置范围
- type 节点类型
- last 该节点的子节点的最后一个
- first 该节点的子节点的第一个
- after() 在当前节点的后面插入一个节点,等价
node.parent.insertAfter(node, add) - cleanRaws 代码格式化(保持缩进...)
- clone 节点克隆
- cloneBefore
- cloneAfter
- each 遍历儿子节点
- error 抛出一个错误
- every 条件遍历
- index 获取节点在父节点中的索引
- insertAfter
- insertBefore
- next
- positionInside Convert string index to line/column
- prepend
- push
- raw()
- remove()
- removeAll() Removes all children from the container and cleans their parent properties.
- removeChild
- replaceValues 用于遍历所有子孙节点 decl value
- root()
- some() Returns true if callback returns true for (at least) one of the container’s children.some()
- toJSON() 打印 JSON,使用 JSON.stringify() 有循环依赖
- toString() 获取转换后的 css
- walk() 遍历所有子孙节点,这个接口非常有用哦
- walkAtRules 遍历艾特节点
- walkComments 遍历注释节点
- walkRules 遍历选择器节点
- walkDecls 遍历属性节点
5.1 Rule
- 公共属性方法
- selector 选择器
- selectors 选择器数组

5.2 Declaration
- 公共属性方法
- prop
- value
5.3 Comment
- 公共属性方法
- text
6. 如何使用 postcss 插件
插件用于丰富 postcss 的功能。
插件编写文档
postcss([PluginA({}), PluginB({})])
.process(css, {from: ``, to: ``})
.then(result => {
// do ...
}).catch(error => {
throw new Error(error)
})
7. 如何编写一个 postcss 插件
上文中,我们对 css 处理后生成的 Root 以及其节点下的 Commont,AtRule,Rule, Declaration 有了基本的认识,那么我们是如何获得Root,又将拿这些构造函数做些什么呢。
7.1 css选择器深度校验
const fs = require('fs')
const path = require('path')
const postcss = require('postcss')
const css = fs.readFileSync(path.resolve(__dirname, './main.css'), 'utf8')
const checkDepth = postcss.plugin('check-depth', (opt) => {
opt = opt || { depth: 3 }
return root => {
root.walkRules(rule => {
let selector = rule.selector.replace(/(^\s*)|(\s*$)/g, '')
if (selector.split(/\s/).length > opt.depth) {
throw rule.error(`css selector depth is too long`)
}
})
}
})
postcss([checkDepth({
depth: 3
})])
.process(css, { from: ``, to: `` })
.then(result => {
console.log(result);
})
.catch(error => {
throw new Error(error)
})
7.2 css属性替换
const postcss = require('postcss') // postcss
module.exports = postcss.plugin('px2rem', function(opts) {
opts = opts || {};
return function (root, result) {
root.replaceValues(/\d+px/, { fast: 'px' }, string => {
return opts.ratio * parseInt(string) + 'rem'
})
}
});
通过上述插件代码的示例,可以看出整个流程还是很清晰的
- 重点对象:Root,Commont,AtRule,Rule, Declaration,Result;
- 遍历方法:walkCommonts,walkAtRules,walkRules,walkDels
8. postcss 的运用
- stylelint 的使用
- 扩展 stylelint 规则
- ...见代码
9. 参考文档
postcss 运用及原理的更多相关文章
- 在 CSS 预编译器之后:PostCSS
提到css预编译器(css preprocessor),你可能想到Sass.Less以及Stylus.而本文要介绍的PostCSS,正是一个这样的工具:css预编译器可以做到的事,它同样可以做到. “ ...
- sass和postcss
sass是css预处理器 需要安装node-sass支持 核心是c++编写 集成 sass-loader 把scss装换成css css-loader 找出@import和url()导入的语法,告诉w ...
- webpack的postcss的基本应用
PostCss是什么? PostCSS在webpack中的基本应用 一.PostCss是什么? 如果有深入学习PostCss需求的话可以参考大漠的资料:https://www.w3cplus.com/ ...
- 【前端知识体系-CSS相关】CSS工程化方案
1.如何解决CSS的模块化问题? 使用Less,Sass等CSS预处理器 使用PostCSS插件(postcss-import/precss) 使用webpack处理CSS(css-loader + ...
- 展望未来:使用 PostCSS 和 cssnext 书写 CSS
原文链接:A look into writing future CSS with PostCSS and cssnext 译者:nzbin 像twitter,google,bbc使用的一样,我打算看一 ...
- CSS预编译与PostCSS以及Webpack构建CSS综合方案
CSS全称Cascading Style Sheets(层叠样式表),用来为HTML添加样式,本质上是一种标记类语言.CSS前期发展非常迅速,1994年哈肯·维姆·莱首次提出CSS,1996年12月W ...
- 更便捷的css处理方式-postcss
更便捷的css处理方式-PostCSS 一般来说介绍一个东西都是要从是什么,怎么用的顺序来讲.我感觉这样很容易让大家失去兴趣,先看一下postcss能做点什么,有兴趣的话再往下看,否则可能没有耐心看下 ...
- 19. vue的原理
vue:原理1 => Object.defineProperty 当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Obj ...
- rem原理
rem布局实际上就是实现等比缩放 试想,如果我们的元素在不同的屏幕上可以按照相同的比例来进行缩放就好了. rem的计算原理: 试想把屏幕平均分成10份,那么每一份就是1/10,我们选择每一份的大小是1 ...
随机推荐
- HarmonyOS三方件开发指南(5)——Photoview组件
PhotoView使用说明 1. PhotoView功能介绍1.1 组件介绍: PhotoView是一个继承自Image的组件,不同之处在于:它可以进行图击放大功能,手势缩放功能(暂无 ...
- cts project的创建修改和删除
事务码:SPRO_ADMIN进入 项目管理界面,点击工具栏创建项目(F5),弹出对话框,输入项目名称,回车确定. 标题中输入项目的描述.点击保存.如图: 点击图片放大 注:要想此项目在CTS建立请求的 ...
- 利用JavaUDPSocket+多线程模拟实现一个简单的聊天室程序
对Socket的一点个人理解:Socket原意是指插座.家家户户都有五花八门的家用电器,但它们共用统一制式的插座.这样做的好处就是将所有家用电器的通电方式统一化,不需要大费周章地在墙壁上凿洞并专门接电 ...
- 微服务网关1-Spring Cloud Gateway简介
一.网关基本概念 1.API网关介绍 API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各 ...
- 图像Demosaic算法及其matlab实现
由于成本和面积等因素的限定,CMOS/CCD在成像时,感光面阵列前通常会有CFA(color filter array),如下图所示,CFA过滤不同频段的光,因此,Sensor的输出的RAW数据信号包 ...
- ROS教程(一):ROS安装教程(详细图文)
ros教程:ros安装 目录 前言 一.版本选择 二.开始安装 2.1 软件中心配置 2.2 添加源 2.3 安装 三.验证ROS 前言 关于ROS(Robot OS 机器人操作系统),估计看这个教程 ...
- 开源AwaitableCompletionSource,用于取代TaskCompletionSource
1 TaskCompletionSource介绍 TaskCompletionSource提供创建未绑定到委托的任务,任务的状态由TaskCompletionSource上的方法显式控制,以支持未来的 ...
- jdk安装简洁版
一.jdk是what? jdk其实是个软件语言开发包,包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具. 没有JDK的话,无法编译Java程序(指java源码.java文件),如果想 ...
- Golang拼接字符串的5种方法及其效率_Chrispink-CSDN博客_golang 字符串拼接效率 https://blog.csdn.net/m0_37422289/article/details/103362740
Different ways to concatenate two strings in Golang - GeeksforGeeks https://www.geeksforgeeks.org/di ...
- 我们都可以把它放 Sidecar 容器中,这样微服务具备了 Super power,一种超能力
云原生时代,微服务如何演进? 原创 李响 阿里技术 2020-08-28 https://mp.weixin.qq.com/s/KQG2U8_aotDL4YFB8ee6Zw 一 微服务架构与云原 ...