PostCSS 速通

2,981 阅读9分钟

什么是 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

在不同的平台还可以配合不同的包来使用

配置 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 原理

core.png

整个流程在图上看比较清晰,不过我们还是深入看一下各个流程都做了什么

  • 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 提供的 nextTokenback 方法将一个或者多个 token 构建部分 AST 为 Node

    PostCSS 有多种 Node 类型,都可以用在插件里

  • Processor 流程

    Processor 流程其实就是调用插件的过程,他可以初始化插件并异步的提供语法转化后的接口,同时暴露一些 API 接口

  • Stringifier 流程

    Stringifier 流程将修改后的 AST 语法再转化为纯净的 CSS 语法

现有的 PostCSS 插件

截止到目前,PostCSS 有 200 多个插件。下方仅列出一些官方推荐的插件,所有的插件列表可以查看插件目录

解决全局 CSS 的问题

  • postcss-use 允许你在 CSS 里明确地设置 PostCSS 插件,并且只在当前文件执行它们。
  • postcss-modulesreact-css-modules 可以自动以组件为单位隔绝 CSS 选择器。
  • postcss-autoreset 是全局样式重置的又一个选择,它更适用于分离的组件。
  • postcss-initial 添加了 all: initial 的支持,重置了所有继承的样式。
  • cq-prolyfill 添加了容器查询的支持,允许添加响应于父元素宽度的样式.

提前使用先进的 CSS 特性

更佳的 CSS 可读性

  • precss 囊括了许多插件来支持类似 Sass 的特性,比如 CSS 变量,套嵌,mixins 等。
  • postcss-sorting 给规则的内容以及@规则排序。
  • postcss-utilities 囊括了最常用的简写方式和书写帮助。
  • short 添加并拓展了大量的缩写属性。

图片和字体

提示器(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 有一个基础的了解,在此之上,如果遇到合适的场景,也能使用起来,那就是对我最大的鼓励。