Bootstrap

PostCSS分享

前沿背景

我们在书写 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方面解读:

  1. PostCss 本身
  2. PostCss 衍生的生态系统

从PostCSS本身来说:

  • 它本身只是一个API,自身并不会改变CSS
  • 作为一个API它可以创建任何需要的插件和工具

从PossCSS衍生的生态系统来说

PossCSS是预处理器吗?不是哦。
PossCSS是后处理器吗?还不是哦。
PossCSS是未来的新语法吗?还还不是哦。
那为什么有人说他像呢。他的定位到底是什么呢?

划重点来了!!!:postCss 具体做的事取决于开发者使用了什么插件。

它可以通过相应的插件去实现类似 sass 等预处理器的功能,如: precss。
也可以通过相应的插件执行后置处理器的工作,如:autoprefixer。
还可以通过相应的插件去编写未来的新语法(css4),如:cssnext。
它的好处还不止这些。。。。
那么PostCSS有特别之处在哪呢?

PostCSS的特别之处

  1. 对Source Map支持更好(它能够读取和解析从之前转换步骤生成的映射,自动检测你期望的格式,并且输出外联和内联映射)
  2. 创建自己的插件,且具可访问性
  3. 支持未来的css: 使用cssnext书写未来的css
  4. 使用 CSS 语法,容易进行模块化,贴近 CSS 的未来标准
  5. 可以与许多流行工具构建无缝部署(grunt,gulp,webpack)
  6. 可以像使用CSS一样使用PostCSS,也可以与less或者sass一起使用
  7. 编译速度大大提升(3倍以上的处理速度,其一只需要加载需要的插件;其二它是运行在JavaScript上,可以使用benchmark运行检测这些基准)
  8. 不依赖于任何预处理器就具备创建一个库的能力(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
autoprefixerpostcss-cssnextprecss三个插件为例

package.json配置如下:
在这里插入图片描述

  1. 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;
}
  1. 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 预处理器 中得到支持
  • 原生 CSSCSS 后处理器 的组合成为新选择
;