前沿背景
我们在书写 css 的时候其实经历了以下几个阶段:
- 手写原生 CSS
- 使用预处理器 Sass/Less/stylus
- 使用 PostCSS
- 使用 css modules
- 使用 css in js
原生css缺点:
- 语法不够强大,不能够嵌套书写,不利于模块化开发
- 没有变量和逻辑上的复用机制,导致在css的属性值中只能使用字面量形式,以及不断重复书写重复的样式,导致难以维护
面对以上问题,为了能够让css具备js的可复用性,灵活性、模块化开发以及更好的管理样式文件,像sass,less,stylus这样的css预处理器就应运而生。
了解下两种处理器的大概定义:
- 预处理器:按照预处理器的语法来写css的代码,最后交由预处理器的编译器编译成一个传统的css
- 后处理器:我们把css代码写完之后,交由它去处理,添加一些必要的属性
预处理器的优点:
- 可以提供 CSS 缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性,大大提高了开发效率,比如变量,函数,运算,条件语句,混合等。 CSS预编译的缺点
- 不支持未来的css
- 引入了编译的过程
- 采用各自预处理器框架语法DSL,有一定的学习成本,框架耦合度高,复杂度高
- 全局变量的污染问题,在多人开发过程当中,定义选择器时,或变量时需要顾及到其他地方是否使用了同样的命名
- Css 预处理器的代码是无法直接运行于浏览器的,所以我们还需要进行编译解析成为 Css 文件。以sass为例,目前前端一般会使用gulp,webpack等构建工具,webpack构建必须安装sass-loader,而sass-loader又依赖于node-sass,要知道node-sass安装速度极其慢,特别是使用window系统开发时,低版本的node经常会出现node-sass安装不成功的情况。
于是后出现了比较典型的是PostCSS。
anyway,终极目的:让你的代码更可靠(Bug更少、兼容性更高甚至功能更强大)
接下来瞅瞅PostCSS到底是何方神圣吧!!!
PostCSS是什么?
来自于PostCSS自身项目在github上的描述:
PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more
翻译过来就是:
postcss是使用用js插件转换样式的工具。这些插件可以校验css,支持variables和mixins,编译未来css语法,内联图片等等
世人关于他的描述:
有人说,他是CSS界的Babel
有人说,他是CSS界的Webpack
有人说,他是用 JavaScript 转换 CSS 的工具
有人说,他是一个平台,允许强大的插件在它上面跑,简化编程
有人说,他功能似预处理器 有人说,他功能似后处理器
有人说,他书写似未来的新语法
anyway,众说纷云,个人认为可以从2方面解读:
- PostCss 本身
- PostCss 衍生的生态系统
从PostCSS本身来说:
- 它本身只是一个API,自身并不会改变CSS
- 作为一个API它可以创建任何需要的插件和工具
从PossCSS衍生的生态系统来说
PossCSS是预处理器吗?不是哦。
PossCSS是后处理器吗?还不是哦。
PossCSS是未来的新语法吗?还还不是哦。
那为什么有人说他像呢。他的定位到底是什么呢?
划重点来了!!!:postCss 具体做的事取决于开发者使用了什么插件。
它可以通过相应的插件去实现类似 sass 等预处理器的功能,如: precss。
也可以通过相应的插件执行后置处理器的工作,如:autoprefixer。
还可以通过相应的插件去编写未来的新语法(css4),如:cssnext。
它的好处还不止这些。。。。
那么PostCSS有特别之处在哪呢?
PostCSS的特别之处
- 对Source Map支持更好(它能够读取和解析从之前转换步骤生成的映射,自动检测你期望的格式,并且输出外联和内联映射)
- 创建自己的插件,且具可访问性
- 支持未来的css: 使用cssnext书写未来的css
- 使用 CSS 语法,容易进行模块化,贴近 CSS 的未来标准
- 可以与许多流行工具构建无缝部署(grunt,gulp,webpack)
- 可以像使用CSS一样使用PostCSS,也可以与less或者sass一起使用
- 编译速度大大提升(3倍以上的处理速度,其一只需要加载需要的插件;其二它是运行在JavaScript上,可以使用benchmark运行检测这些基准)
- 不依赖于任何预处理器就具备创建一个库的能力(Cory Simmons当时使用Stylus和Sass编写了一个网格系统,他的用户群体只对应使用Stylus和Sass的用户。随后他使用PostCSS移植了这个网格系统,这也意味着现在每个人都可以使用了,包手Stylus、Sass用户,甚至是使用LESS或不使用任何预处理器的人)
话不多说,接下来看看他是如何工作的吧!!!
PostCSS是如何处CSS的
PostCSS架构大致如下图:
- parser过程:将css字符串解析成可供我们操作的JavaScript对象
- processor过程:我们应用postcss插件、或是自定义插件,都是在这个过程中,根据postcss提供的API,对parser生成的js对象做相应调整
- stringfier过程:将我们处理后的js对象, 从 root 开始,层序遍历 AST 树,根据节点类型,拼接节点的数据为css字符串
PostCSS本身是一个NodeJs模块,它将CSS转换成AST(抽象语法树),对应的是JavaScript对象,然后通过插件遍历AST,进行增加,删除,修改,最后再生成CSS文件,这就是整个流程,跟babel的架构非常相似。
来大概看一眼解析后的AST结构吧
- Root: 继承自 Container。AST 的根节点,代表整个 css 文件
- AtRule: 继承自 Container。以 @ 开头的语句,核心属性为 params,例如: @import url(‘./default.css’),params 为url(‘./default.css’)
- Rule: 继承自 Container。带有声明的选择器,核心属性为 selector,例如: .color2{},selector为.color2
- Comment: 继承自 Node。标准的注释/* 注释 */ 节点包括一些通用属性:
- type:节点类型
- parent:父节点
- source:存储节点的资源信息,计算 sourcemap
- start:节点的起始位置
- end:节点的终止位置 raws:存储节点的附加符号,分号、空格、注释等,在 stringify 过程中会拼接这些附加符号
PostCSS插件列表
- autoprefixer (添加浏览器前缀)
- precss(可以像写预处理器一样写css)
- cssnext (可以使用最新的css语法)
- cssnano(压缩css)
- stylelint (CSS 检测器,支持新css语法校验)
- postcss-modules
…
话不多说,直接看怎么使用吧!项目目录如下图所示!
src文件夹下,index.css配置
/* nextcss */
:root {
--mainColor: red;
}
.color {
color: var(--mainColor);
}
/* 嵌套 */
.precss {
width:100%;
&::before {
content: '';
}
}
/* 变量 */
$blue:#056ef0;
$border_comn: 1px solid red;
.variable{
color:$blue;
border: $border_comn;
}
/* 条件循环 */
$column_layout: 2;
.column{
@if $column_layout == 2{
width: 50%;
float: left;
}@else{
width: 100%;
}
}
/* 循环 */
@for $i from 1 to 3{
p:nth-of-type($i){
margin-left: calc(100% / $i);
}
}
/* 添加浏览器前缀 */
.autoprefixer{
display: flex;
}
src文件夹下,index.js配置
import './index.css'
const div = document.createElement('div')
div.innerHTML = 'hello postcss'
div.className = 'autoprefixer'
document.body.append(div)
PostCSS与工具的结合
本文主要介绍PostCSS与gulp和webpack的结合使用demo
以autoprefixer,postcss-cssnext,precss三个插件为例
package.json配置如下:
- PostCSS与webpack(配置)
npm install webpack webpack-cli mini-css-extract-plugin css-loader postcss-loader postcss-cssnext autoprefixer, precss
配置webpack.config.js
要使用到postcss-loader,且必须在css-loader后面
const path = require('path');
const postcss = require('postcss')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const transformColor = require("./postcss-transform-color");
// var root = postcss.parse('a{color:white;} b{width:100px}')
// console.log("root",root)
module.exports = {
entry: './src/index.js',
output: {
filename:'index.js',
path:path.resolve(__dirname,'dist/webpack')
},
module: {
rules:[
{
test: /\.css$/,
use:[{
loader: MiniCssExtractPlugin.loader
},
'css-loader',
{
loader: "postcss-loader",
// options: {
// ident: 'postcss',
// sourceMap: true,
// plugins:[
// transformColor({ colorMap: [{ source: '#1890ff', target: '#ffbd00' }] })
// ]
// }
}
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css'
})
]
}
那接下来就是让Loader识别到插件配置了,Loader 将会从下面几个地方搜索目录树来寻找配置文件:
- package.json 中的 postcss 属性
- JSON 或者 YAML 格式的 .postcssrc 文件
- .postcss.json、.postcss.yaml、.postcss.yml、.postcss.js 或者 .postcss.cjs 文件
- postcss.config.js 或者 postcss.config.cjs 导出一个对象的 CommonJS 模块(推荐)
本demo以配置postcss.config.js为例
const transformColor = require("./postcss-transform-color");
module.exports = {
plugins: [
// require('autoprefixer'),
require('precss'),
require('postcss-cssnext'),
// transformColor({ colorMap: [{ source: '#1890ff', target: '#ffbd00' }] })
]
}
执行npm run build之后生成的 webpack/index.css如下
/* nextcss */
.color {
color: red;
color: red;
}
/* 嵌套 */
.precss {
width:100%
}
.precss:before {
content: '';
}
/* 变量 */
.variable{
color:#056ef0;
border: 1px solid red;
}
/* 条件循环 */
.column{
width: 50%;
float: left
}
/* 循环 */
p:nth-of-type(1){
margin-left: 100%;
}
p:nth-of-type(2){
margin-left: 50%;
}
p:nth-of-type(3){
margin-left: 33.33333%;
}
/* 添加浏览器前缀 */
.autoprefixer{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
- PostCSS与Gulp(配置)
安装gulp,gulp-postcss,postcss-cssnext,autoprefixer, precss
配置gulpfile.js
var gulp = require('gulp');
var transformColor = require('./postcss-transform-color');
var cssnext = require('postcss-cssnext');
var autoprefixer = require('autoprefixer');
var postcss = require('gulp-postcss');
var precss = require('precss');
gulp.task('css', function(){
var processors = [
autoprefixer,
cssnext,
precss,
//transformColor({ colorMap: [{ source: '#1890ff', target: '#ffbd00' }] })
];
return gulp.src('./src/index.css')
.pipe(postcss(processors))
.pipe(gulp.dest('./dist/gulp'));
});
执行gulp css,会在dist/gulp文件夹下生成被postcss处理过的index.css
/* nextcss */
.color {
color: red;
color: red;
}
/* 嵌套 */
.precss {
width:100%
}
.precss:before {
content: '';
}
/* 变量 */
.variable{
color:#056ef0;
border: 1px solid red;
}
/* 条件循环 */
.column{
width: 50%;
float: left
}
/* 添加浏览器前缀 */
.autoprefixer{
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
如何编写一个postcss插件
带有 postcss- 前缀的清晰的命名
- 只做一件事,并将它做好
- 不要使用 mixins
- 通过 postcss.plugin 创建插件
module.exports = postcss.plugin('plugin-name', function (opts) {
return function (root, result) {
// 插件代码
};
});
在后台前端应用的开发中,经常会用到各类 UI 组件库,假如项目中用到了知名的 antd,但是需要对应用的各个基础组件做一次换肤,而 antd 只有定制化的几种色号可选,这次要做的是将基础组件的色号都换成美团黄,所以 antd 没法满足需求。当然我们可以在样式表中手动改写 antd 的样式以达到目标,但是这并不方便。我们沿着自动化的思路想,可以在打包过程中对所有样式做统一处理并做一层转换,而样式的解析转换刚好是 loader 的工作,在此实现这一思路会非常合适
以下图class为例,解析出右边的AST结构
转换后的结构就像包含了很多层级树一样,第一层是 root 节点,每个 css 文件就相当于一个 root 节点,每个 root 节点包含的是子节点集合即 type 为 rule 的节点,对应于 css 中的样式块,每个样式块即 type 为 decl 包含书写的 css 键值对集合,prop 对应于健,而 value 就是值了。可以想到,只要遍历获取这些值再转换目标值为需要的值,即获取到 decl 节点中的 value 并进行替换
PostCss 提供了很多 API 方便操作转换后 AST 树,此次分析后只用到如下遍历相关 API
- walk: 遍历所有节点信息
- walkAtRules: 遍历所有 atrule 类型节点
- walkRules: 遍历所有 rule 类型节点
- walkComments: 遍历所有 comment 类型节点
- walkDecls: 遍历所有 decl 类型节点
在根目录下新建postcss-transform-color.js,插件名为postcss-transform-color
const postcss = require('postcss');
const transformColorPlugin = postcss.plugin('postcss-transform-color', opts => {
opts = opts || {};
const { colorMap } = opts;
return root => {
root.walkRules(rule => {
rule.walkDecls(function (decl, i) {
colorMap.forEach(({ source, target }) => {
if (decl.value.indexOf(source) !== -1) {
decl.value = decl.value.replace(source, target);
}
});
});
});
};
});
module.exports = transformColorPlugin
首先通过 PostCss 转译为如下 AST 并深度遍历,先遍历 rule 节点再遍历 decl 节点,最后匹配 value 是否符合 config 中的 source,是则替换为美团黄。
如果在 webpack 中使用自定义的插件,则需要在 postcss 的 loader 中进行自定义插件的配置,也可以在postcss.config.js中配置
执行npm run build后,
变为:
如何选择开发模式呢
PostCSS看起来有如此多的优点,那么项目中要用吗?用的话会带来什么样的变化呢?
那么,最明显的变化应该是开发模式的变化!
原来的开发模式是这样的:
DSL 源代码 -> 生产环境 CSS
与原来相比,新的 开发模式 最大的变化是面向 标准 CSS 编程,将 兼容性、优化 部分交给 CSS 后处理器 自动完成
DSL 源代码 -> 标准 CSS -> 生产环境 CSS
等到众多 CSS 未来标准 在 CSS 后处理器 层面实现之后,部分项目甚至可以回归到使用 标准 CSS 编程的模式:
标准 CSS(包含未来标准的后处理器实现)-> 生产环境 CSS
以下有一些简单对比:
推荐 CSS 预处理器 与 CSS 后处理器 同时使用,各自做他们最擅长的部分。
网上有大神有这样的预测,可以瞅瞅!
- 越来越多专注于 单一功能 的小型 CSS 工具库
- CSS 样式库 从 整体方案 到 模块化组合方案 转变
- 部分 CSS 未来标准 在 CSS 预处理器 中得到支持
- 原生 CSS 和 CSS 后处理器 的组合成为新选择