初识postcss
css是web开发中重要的一部分,然而css规范仍存在一些浏览器兼容性问题,由此出现了sass、less、stylus等css预处理器,提高了开发人员的效率。
postcss的官方定义:
A tool for transforming CSS with JavaScript
一个用javascript来处理css语法的工具。postcss本身不会对css文件进行修改,它只将css文件转化为抽象语法树(abstract syntax tree,后简称ast),然后插件对语法树进行处理,最后由postcss将ast还原为普通css,所以postcss对css文件的修改都是基于插件来实现的。
postcss可以做什么
截止到目前,postcss已有200多个插件,列举一些比较出名的插件
-
autoprefixer
它从caniuse网站上的数据,自动添加浏览器前缀到css中,帮助开发人员解决浏览器兼容问题。
-
postcss-preset-env
支持css的最新特性,并兼容大多数浏览器。
-
postcss-modules
模块化css代码,为选择器提供命名空间后缀。
-
precss
解析一些类sass语法,包括:变量、嵌套、mixins等。
-
stylelint
css语法检查器,可以帮助开发者检查css文件中的语法错误。
更多插件地址:github.com/postcss/pos…
postcss用法
postcss可以与各种前端打包工具相结合使用,例如webpack、gulp、grunt、rollup...
webpack
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
}
]
}
}
// postcss.config.js
module.exports = {
plugins: [
require('precss'),
require('autoprefixer')
]
}
也可以直接使用postcss提供的js api直接处理css。
JS API
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const precss = require('precss')
const css = `
.test {
color: #fff;
}
`;
postcss([precss, autoprefixer])
.process(css, { from: 'src/app.css', to: 'dest/app.css' })
.then(result => {
console.log(result.css);
});
从零开始开发一个postcss插件
抽象语法树
开发postcss插件前,首先要理解的一个概念就是文章开头提到的抽象语法树,postcss将css文本转化为ast,javascript可以通过一系列api来操作、改变ast,再由解析器将ast解析成普通目标css文本。
css文件:
body {
margin: 0;
}
转化为ast,对应的json:
{
"raws": {
"semicolon": false,
"after": ""
},
"type": "root",
"nodes": [
{
"raws": {
"before": "",
"between": " ",
"semicolon": true,
"after": "\n"
},
"type": "rule",
"nodes": [
{
"raws": {
"before": "\n\t",
"between": ": "
},
"type": "decl",
"source": {
"start": {
"line": 2,
"column": 2
},
"input": {
"css": "body {\n\tmargin: 0;\n}",
"id": "<input css 32>"
},
"end": {
"line": 2,
"column": 11
}
},
"prop": "margin",
"value": "0"
}
],
"source": {
"start": {
"line": 1,
"column": 1
},
"input": {
"css": "body {\n\tmargin: 0;\n}",
"id": "<input css 32>"
},
"end": {
"line": 3,
"column": 1
}
},
"selector": "body"
}
],
"source": {
"input": {
"css": "body {\n\tmargin: 0;\n}",
"id": "<input css 32>"
},
"start": {
"line": 1,
"column": 1
}
}
}
其中,ast的第一级是一个root类型的节点,type除root外还包括rule、decl、atrule、comment,nodes属性表示节点下的子节点。
postcss为我们提供了一些基本的ast操作方法:
- walk:遍历所有节点信息
- walkAtRules:遍历所有atrule类型节点
- walkRules:遍历所有rule类型节点
- walkComments:遍历所有comment类型节点
更多节点信息参照postcss api文档:api.postcss.org/
插件开发
我们开发一个将像素值乘以2的插件。
const postcss = require('postcss');
const postcssPluginParseMargin = postcss.plugin('postcss-parse-margin', function(){
return function(root){
root.walkDecls(function(decl){
decl.value = decl.value.replace(/(\d*\.?\d+)\s*px/, function(match, numStr) {
return Number(numStr) * 2 + 'px';
});
});
}
});
const css = `
.test {
font-size: 2.5px;
}
`;
postcss([postcssPluginParseMargin]).process(css).then(function(res){
console.log(res.css);
});
/**
.test {
font-size: 5px;
}
*/
我们调用walkDecls函数,从root节点开始遍历所有的decl类型节点,取出decl.value中的像素值,乘以2后替换掉原有decl.value,这样就实现了一个简单的postcss插件。
实际问题:postcss转义less语法
less是一种css预处理器,为css扩展了更多的功能,如变量、继承、mixins、函数等。如果我们想基于less的语法使用postcss的插件,一种方案是使用less将less语法转义成css语法后,再用postcss处理转义后的css文件,但这种方案由于要经历两次语法树的生成过程,比较耗时。与sass不同,less本身就是js编写的,因此我们可以基于less自带的语法树解析器,将less语法树直接转化为postcss语法树,就可以无缝衔接使用其他postcss插件。
这个轮子已经有大神帮我们造好,叫做postcss-less-engin,只是项目已有一年多未更新,less还是基于2.7.1版本,更新到3.0.0以上会报错。查阅了less的changelog,发现原因是3.0.0以后,less解析器中的Directive类重命名为AtRule,Rule重命名为Declaration。bug修复好已提pr等待作者更新,大家如有需要支持最新less可以暂时使用postcss-less-engine-latest,git地址:github.com/Crunch/post…
插件用法:
const postcss = require('postcss');
const postCssLessEngine = require('postcss-less-engine');
const css = `
@color: red;
.test {
color: @color;
}
`;
postcss([
postCssLessEngine()
]).process(
css, {
parser: postCssLessEngine.parser
}
).then((result)=>{
console.log(result.css);
/**
.test {
color: red;
}
*/
});
如果只想支持less的部分特性,完全可以自己编写postcss插件来支持,postcss-less插件可以用来识别less语法,为postcss抽象语法树添加less标识,但是不会转换原有代码,转换工作交给其他postcss插件来完成,大家有兴趣可以开发适合自己需求的插件。
git地址:github.com/shellscape/…