加班加点忙到元旦前一天终于有空写点什么东西,原本打算记录一下年终项目的思考,然后发现没法理清思路动不了键盘。然后写下 PostCSS 相关好了,不会涉及具体 API 的使用,主要是“是什么”,“做什么”,“为什么”。

是什么

引用 官网 的描述,然后自己又给补充了一句,PostCSS 差不多就是这样了

  • 以 JavaScript 插件的形式对样式文本进行操作
  • 区别于 Stylus,SASS 等预处理工具

做什么

PostCSS 能做的东西就很多啦。 官网 高亮的 feature 有下面几点:

  • Autoprefixer
  • cssnext
  • CSS Module
  • stylelint

为什么

因为简单灵活方便呀。举个简单的栗子,我们通过工具把样式文件的 px 单位转换为 rem, 这样子我们在写样式的时候就不用人肉计算或者 mixin 那么麻烦啦

/* raw */
.foo {
margin: 10px 5px;
width: 75px;
height: 75px;
}
/* expected */
.foo {
margin: 10rem 5rem;
width: 75rem;
height: 75rem;
}

然后分别用三种方法去做这个事情:

  1. 基于正则表达式
  2. 基于 css parser
  3. 基于 PostCSS

正则表达式

通过正则表达式处理样式文本的步骤如下:

  1. 通过正则表达式命中所有符合 px 的片段
  2. 对命中内容进行替换

不负责任的代码如下:

const content = fs.readFileSync('style.css', 'utf8')
const reg = /(\d+)px/g
content.replace(reg, '$1rem')

然后发现基于正则表达式实现的话有如下缺点:

  1. 针对性强
  2. 要求正则兼容各种情况,容错性不足

css parser

抽象语法树(abstract syntax tree 或者缩写为 AST),或者语法树(syntax tree),是源代码的抽象语法结构的树状表现形式,这里特指编程语言的源代码。 树上的每个节点都表示源代码中的一种结构。—— 来自维基百科

这里使用一个 Node 的 css 模块,将样式文本转换为抽象语法树。通过遍历样式文本对应的 AST 对象,可以正确遍历所有的节点,并进行操作。步骤如下:

  1. 通过样式文本创建 AST 对象
  2. 通过遍历算法遍历所有节点,并找到其中包含 px 有效节点
  3. 修改节点的值
  4. 将调整后的 AST 对象转换为样式文本

可以看到对比正则表达式更加灵活和准确,然后需要自己提供遍历算法,并熟悉 AST 对象的结构,使用成本也比较高。

也附上对应的 AST 对象展开,或者有兴趣可以在 这里 自己尝试:

{
"type": "stylesheet",
"stylesheet": {
"rules": [{
"type": "rule",
"selectors": [".foo"],
"declarations": [{
"type": "declaration",
"property": "margin",
"value": "10px 5px",
"position": {
"start": { "line": 22, "column": 17 },
"end": { "line": 22, "column": 33 }
}
},
// tow more nodes
],
"position": {
"start": { "line": 21, "column": 15 },
"end": { "line": 25, "column": 16 }
}
}],
"parsingErrors": []
}
}

PostCSS

与 css-parse 的做法一样,PostCSS 将样式文本转换为 AST,但同时又提供了丰富的接口去遍历、操作节点。这就使得 PostCSS 在处理相关的事情时游刃有余。具体步骤如下:

  1. 创建 PostCSS 的 Root 对象
  2. 遍历每个样式属性,并将 px 替换为 rem
  3. Root 对象转换为字符串

不负责的代码如下:

const content = fs.readFileSync('style.css', 'utf8')
const root = postcss.parse(content)
const processor = decl => {
decl.value = parseInt(decl.value) + 'rem'
}
root.walkDecl(processor)
root.toString()

甚至更为简单粗暴:

const content = fs.readFileSync('style.css', 'utf8')
const root = postcss.parse(content)
root.replaceValues(/\d+px/, { fast: 'px' }, string => {
return parseInt(string) + 'rem';
})
root.toString()

小结

基于 PostCSS 可以很方便地操作 CSS,同时它的插件的开发和使用也都十分简单。很多时候自己动手搞一个插件的成本很低,但却能够解决日常中很多问题,如插入媒体查询样式,图片内联,精灵图计算等。又是玩的开心~