什么是 PostCSS
定义
PostCSS 是一个使用 JS 插件转换样式的工具(a tool for transforming styles with JS plugins)
这些插件可以检查(lint)你的 CSS,支持 CSS Variables 和 Mixins,编译尚未被浏览器广泛支持的先进的 CSS 语法,内联图片,以及其它很多优秀的功能
与 less / scss 的区别
我们先来回顾一下预处理器的用法:通过一定的语法、方法、函数,经过预先编译生成符合浏览器的 css 格式
而 PostCSS 则没有特定的语法,他本身不含有任何内容,而是要结合众多的插件来实现各种功能,例如:autoprefix 插件可以实现某些 css 样式,自动满足多个浏览器平台的写法,另外你也可以对不同的语法进行转换,例如:sass 语法,甚至是你自己设置的语法,例如:一个 @myself 语法
// Input
.example {
transition: all .5s;
}
// Output
.example {
-webkit-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
// Input
.example {
font-size: 14px;
}
// Output
.example {
font-size: 1rem;
}
用别的库比喻的话,就像是 Babel 和 Webpack 的区别,预处理(Babel)将特定语法转为当前的语法,PostCSS(Webpack)则可以通过插件在不修改 CSS 写法的前提下拆分、重写、转换,最后生成新的 CSS
因此,PostCSS 可以认为是一个平台,依靠丰富多样的插件来工作
PostCSS 快速上手
安装 PostCSS
npm install postcss
在不同的平台还可以配合不同的包来使用
- Webpack
postcss-loader
- Gulp
gulp-postcss
- command-line
postcss-cli
- Deno
https://deno.land/x/postcss/mod.js
- Grunt
grunt-postcss
- HTML
posthtml-postcss
- Stylus
poststylus
- Rollup
rollup-plugin-postcss
- Brunch
postcss-brunch
- Broccoli
broccoli-postcss
- Meteor
postcss
- ENB
enb-postcss
- Taskr
taskr-postcss
- Start
start-postcss
- Connect/Express
postcss-middleware
配置 PostCSS
创建 postcss.config.js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')
]
}
使用 PostCSS
-
一些平台,例如:webpack,通过添加 postcss-loader 以及插件配置,就可以直接使用 PostCSS
-
命令行,使用
postcss-cli
postcss --use autoprefixer -c options.json -o main.css css/*.css
-
其他环境,使用 JS API
const autoprefixer = require('autoprefixer') const postcss = require('postcss') const precss = require('precss') const fs = require('fs') fs.readFile('src/app.css', (err, css) => { postcss([precss, autoprefixer]) .process(css, { from: 'src/app.css', to: 'dest/app.css' }) .then(result => { fs.writeFile('dest/app.css', result.css) if ( result.map ) fs.writeFile('dest/app.css.map', result.map) }) })
可以看出,PostCSS 通过加载多个插件后,暴露出一个 process 方法,将 css 传入后就可得到转化后的 css,同时也是一个支持 Promise 的调用链
PostCSS 原理
整个流程在图上看比较清晰,不过我们还是深入看一下各个流程都做了什么
-
Tokenizer 流程
PostCSS 采用的是 词法分析/解析步骤(lexical analysis/parsing steps),具体来说,是 source string → tokens → AST 的一个过程,因为在有些情况下 string 转化为 tokens 阶段,比较耗时,有些情况,转化为 AST 比较耗时,因此,PostCSS 拆分成了两个阶段,更好的优化编译速度
转化为 token 阶段是什么?首先我们要理解 token 代表着什么数据格式,token 代表一些特殊的语法例如:
at-rule
,comment
或者word
,还有一些代码的位置信息,例如考虑如下语法:.className { color: #FFF; }
转化为 token 则是:
[ ["word", ".className", 1, 1, 1, 10] ["space", " "] ["{", "{", 1, 12] ["space", " "] ["word", "color", 1, 14, 1, 18] [":", ":", 1, 19] ["space", " "] ["word", "#FFF" , 1, 21, 1, 23] [";", ";", 1, 24] ["space", " "] ["}", "}", 1, 26] ]
可以看出,token 就是把一句代码的具体信息都表示出来了,需要注意的是,空格没有位置信息,具体每个数组里的数据代表的信息如下:
const token = [ // token 类型 'word', // 匹配到的文字 '.className', // 这两个数字代表 token 的开始位置,第一个是行数,第二个是列数 1, 1, // 这两个数字代表结束位置,同样第一个是行数,第二个是列数 1, 10 ]
-
Parse 流程
Parse 是分析 CSS 语法的主要结构。解析器生成一种称为抽象语法树 (AST) 的结构,稍后可以通过插件对其进行转换。
他使用 Tokenizer 提供的
nextToken
和back
方法将一个或者多个 token 构建部分 AST 为 NodePostCSS 有多种 Node 类型,都可以用在插件里
-
Processor 流程
Processor 流程其实就是调用插件的过程,他可以初始化插件并异步的提供语法转化后的接口,同时暴露一些 API 接口
-
Stringifier 流程
Stringifier 流程将修改后的 AST 语法再转化为纯净的 CSS 语法
现有的 PostCSS 插件
截止到目前,PostCSS 有 200 多个插件。下方仅列出一些官方推荐的插件,所有的插件列表可以查看插件目录
解决全局 CSS 的问题
postcss-use
允许你在 CSS 里明确地设置 PostCSS 插件,并且只在当前文件执行它们。postcss-modules
和react-css-modules
可以自动以组件为单位隔绝 CSS 选择器。postcss-autoreset
是全局样式重置的又一个选择,它更适用于分离的组件。postcss-initial
添加了all: initial
的支持,重置了所有继承的样式。cq-prolyfill
添加了容器查询的支持,允许添加响应于父元素宽度的样式.
提前使用先进的 CSS 特性
autoprefixer
添加了 vendor 浏览器前缀,它使用 Can I Use 上面的数据。postcss-preset-env
允许你使用未来的 CSS 特性。
更佳的 CSS 可读性
precss
囊括了许多插件来支持类似 Sass 的特性,比如 CSS 变量,套嵌,mixins 等。postcss-sorting
给规则的内容以及@规则排序。postcss-utilities
囊括了最常用的简写方式和书写帮助。short
添加并拓展了大量的缩写属性。
图片和字体
postcss-assets
可以插入图片尺寸和内联文件。postcss-sprites
能生成雪碧图。font-magician
生成所有在 CSS 里需要的@font-face
规则。postcss-inline-svg
允许你内联 SVG 并定制它的样式。postcss-write-svg
允许你在 CSS 里写简单的 SVG。
提示器(Linters)
stylelint
是一个模块化的样式提示器。stylefmt
是一个能根据stylelint
规则自动优化 CSS 格式的工具。doiuse
提示 CSS 的浏览器支持性,使用的数据来自于 Can I Use。colorguard
帮助你保持一个始终如一的调色板。
其它
postcss-rtl
在单个 CSS 文件里组合了两个方向(左到右,右到左)的样式。cssnano
是一个模块化的 CSS 压缩器。lost
是一个功能强大的calc()
栅格系统。rtlcss
镜像翻转 CSS 样式,适用于 right-to-left 的应用场景。
语法
PostCSS 可以转化样式到任意语法,不仅仅是 CSS。 如果还没有支持你最喜欢的语法,你可以编写一个解释器以及(或者)一个 stringifier 来拓展 PostCSS。
sugarss
是一个以缩进为基础的语法,类似于 Sass 和 Stylus。postcss-syntax
通过文件扩展名自动切换语法。postcss-html
解析类 HTML 文件里<style>
标签中的样式。postcss-markdown
解析 Markdown 文件里代码块中的样式。postcss-jsx
解析源文件里模板或对象字面量中的CSS。postcss-styled
解析源文件里模板字面量中的CSS。postcss-scss
允许你使用 SCSS (但并没有将 SCSS 编译到 CSS)。postcss-sass
允许你使用 Sass (但并没有将 Sass 编译到 CSS)。postcss-less
允许你使用 Less (但并没有将 LESS 编译到 CSS)。postcss-less-engine
允许你使用 Less (并且使用真正的 Less.js 把 LESS 编译到 CSS)。postcss-js
允许你在 JS 里编写样式,或者转换成 React 的内联样式/Radium/JSS。postcss-safe-parser
查找并修复 CSS 语法错误。midas
将 CSS 字符串转化成高亮的 HTML。
如何开发 PostCSS 插件
官方列出了很多已有的插件,例如,如果你总是忘记加一些 hack css,可以使用 postcss-flexbugs-fixes
,你也可以为一些已有的插件添加新特性,例如 postcss-preset-env
有很多 polyfills 的 CSS,你可以提交新的废弃的语法进去
一个基本的 index.js
module.exports = (opts = {}) => {
// Plugin creator to check options or prepare caches
return {
postcssPlugin: 'PLUGIN NAME'
// Plugin listeners
}
}
module.exports.postcss = true
找到 Nodes
大部分插件只做两件事
- 在 CSS 中找某些语法
- 修改找到的语法
PostCSS 提供很多 node 类型:
- Root: 整个 CSS 树的最上层
- AtRule:
@
开头的语法,比如:@media (screen) {}
- Rule: 选择器语法,比如:
input, button {}
- Declaration: key-value 组合,比如:
color: black
- Comment: 注释
你可以使用通过特定的 Node 方法,找到这些 Node 并做一些处理
module.exports = (opts = {}) => {
return {
postcssPlugin: 'PLUGIN NAME',
Once (root) {
// Calls once per file, since every file has single Root
},
Declaration (decl) {
// All declaration nodes
}
}
}
module.exports.postcss = true
也可以更精确的匹配
Declaration: {
color: decl => {
// All `color` declarations
}
'*': decl => {
// All declarations
}
},
AtRule: {
media: atRule => {
// All @media at-rules
}
}
监听 Node
每个 Node 都有两种类型的监听事件,例如:Root 是在转换之前调用,RootExit 是在转换之后调用
除了对象方法使用,也可以通过 prepare 方法动态返回监听事件,例如:
module.exports = (opts = {}) => {
return {
postcssPlugin: 'vars-collector',
prepare (result) {
const variables = {}
return {
Declaration (node) {
if (node.variable) {
variables[node.prop] = node.value
}
},
OnceExit () {
console.log(variables)
}
}
}
}
}
修改 Nodes
PostCSS 提供了一些 Node 方法,用于获取 Node 信息、修改 Node、移除 Node,例如:
Declaration (node, { Rule }) {
let newRule = new Rule({ selector: 'a', source: node.source })
node.root().append(newRule)
newRule.append(node)
}
每修改一次,这些方法就会被再调用一次,所以为了避免死循环,最好加一些判断条件
Node 方法有两个参数,第一个是 Node 本身,第二个是一个对象,有一些常用的属性,例如:result,可以返回编译后的结果,还有一些特定 Node 的对象,例如上例中的 Rule
测试、发布
写一个测试文件,测试你的插件,并通过 npm 发布,这里不再赘述
结语
学习 PostCSS 的契机,是因为用了 tailwindcss
以及想在内网实现一个通用 CSS 样式架构,由于 PostCSS 的文档和教程基本都是英文的,还是花了不少时间,不过他的思路非常值得学习,用法单一,却能实现非常复杂的功能,希望大家看完之后能对 PostCSS 有一个基础的了解,在此之上,如果遇到合适的场景,也能使用起来,那就是对我最大的鼓励。