Skip to main content

PostCSS

CSS后处理器

CSS后处理器是在原生的CSS代码的基础上做处理,常见的处理工作如下:

  1. 兼容性处理:自动添加浏览器前缀(如-webkit--moz--ms-)以确保跨浏览器兼容性。
  2. 代码优化与压缩:移除多余的空格、注释和未使用的规则,以减小CSS文件的大小。
  3. 功能增强:添加新的CSS特性,使开发者能够使用尚未在所有浏览器中实现的CSS功能。
  4. 代码检查与规范:检查CSS代码的质量,以确保代码符合特定的编码规范和最佳实践。

后处理器是有非常多的,如autoprefixercssnanostylelint等,都是在对原生CSS做后处理工作。

但这里就会涉及到一个问题,由于能够对CSS做后处理的工具非常多,此时就需要将原生的CSS先放到一个工具进行处理,处理完成后再放到另一个工具进行处理,以此类推,这种手动操作的方式显然是比较费时费力的。因此期望有一种工具,能够自动化的完成这些后处理工作,而这个工具就是PostCSS

PostCSS

PostCSS是一个使用JavaScript编写的CSS后处理器,它更像是一个平台,类似于Babel。它只负责一件事情,将原生CSS转换为CSS的抽象语法树,之后的事情就完全交由插件去做。

目前整个PostCSS插件生态有200+的插件,每个插件可以处理一种CSS后处理场景。

yarn add postcss autoprefixer -D
const fs = require('fs')
const path = require('path')
const postcss = require('postcss')
const autoprefixer = require('autoprefixer')

const style = fs.readFileSync(
path.resolve(__dirname, './index.css')
)

postcss(
[
autoprefixer({
overrideBrowserslist: 'last 10 versions'
})
])
.process(style, { from: undefined })
.then((res) => {
console.log(res.css)
})

postcss-cli

postcss-cli通过提供一些命令行的命令来简化PostCSS的使用。

yarn add postcss-cli -D

通过postcss-cli所提供的命令来进行文件的编译操作,在package.json里添加如下脚本:

"scripts": {
"build": "postcss .index.css -o ./build.css"
},
  • -o:表示编译后的输出文件,编译后的文件默认是带有源码映射。
  • --no-map:不需要源码映射
  • --watch:用于做文件变化的监听工作,当文件有变化的时候,会自动重新执行命令。注意如果使用了--watch来做源码文件变化的监听工作,那么一般建议把编辑器的自动保存功能关闭掉。

配置文件

要指定配置文件,可项目的根目录下面创建一个名为postcss.config.js的文件,当使用postcss-cli或者构建工具(webpackvite)来进行集成的时候,PostCSS会自动加载配置文件。

Postcss配置文件最主要就是做插件配置

module.exports = {
plugins: [
require("autoprefixer")({
overrideBrowserslist: "last 10 versions",
}),
// other plugins
],
// map: { inline: false },
// syntax: "postcss-scss",
// parser: 'postcss-safe-parser',
// stringifier: ''
};
  • plugins:一个数组,包含要使用到的PostCSS的插件以及相关的插件配置。
  • map:是否生成源码映射,对应的值为一个对象。
  • syntax:用于指定PostCSS应该使用的CSS语法,默认情况下PostCSS处理的是标准的CSS,但是有CSS使用预处理器,此时PostCSS是不认识的,所以这个时候需要安装对应的插件并且在配置中指明syntax
  • parser:配置自定义解析器。Postcss默认的解析器为postcss-safe-parser,负责将CSS字符串解析为CSS抽象语法树。
  • stringifier:自定义字符串化器。用于将CSS抽象语法树转回CSS字符串。

插件顺序

PostCSS的插件是有顺序的。因为一些插件可能依赖于其它插件的输出,可以将plugins对应的数组看作是一个流水线的操作,先交给数组的第一项插件进行处理,之后将处理结果交给数组配置的第二项插件进行处理,以此类推。

autoprefixer

autoprefixer插件可自动添加浏览器厂商前缀,提升CSS的兼容性。

yarn add autoprefixer -D
module.exports = {
plugins: [
require("autoprefixer")({
overrideBrowserslist: "last 10 versions",
}),
],
};

browerslist

browerslist主要是用来配置兼容的浏览器的范围。因为浏览的种类以及版本是非常多的,在做兼容的时候,不可能把所有的浏览器都做兼容,并且也没有意义,所以一般是需要指定范围的。

browserslist包含了一些配置规则,可以通过这些配置规则来指定要兼容的浏览器的范围:

  • last n versions:支持最近的n个浏览器版本。
  • n% :支持全球使用率超过n%的浏览器。
  • cover n%:覆盖n%的主流浏览器。
  • not dead:支持所有“非死亡”的浏览器,已死亡的浏览器指的是那些已经停止更新的浏览器。
  • not ie<11:排除ie 11以下的浏览器。
  • chrome>=n:支持Chrome浏览器大于等于n的版本。

browserslist配置的值所对应的浏览器具体范围

browserslist配置的值是可以有多个,如果有多条规则,可以使用关键词orandnot来指定多条规则之间的关系,可参阅

browserslist的三种配置方式:

// 项目根目录
>1%
last 2 versions
not dead

cssnano

cssnano是一个对CSS压缩的工具。

yarn add cssnano -D
module.exports = {
plugins: [
require("cssnano")
],
};

cssnano一般是不需要传入配置的,因为默认的预设就已经非常好了。但如果确实要进行一些配置也是可以的,因为cssnano本身又是由一些其他的插件组成的:

  • postcss-discard-comments:删除 CSS 中的注释。
  • postcss-discard-duplicates:删除 CSS 中的重复规则。
  • postcss-discard-empty:删除空的规则、媒体查询和声明。
  • postcss-discard-overridden:删除被后面的相同规则覆盖的无效规则。
  • postcss-normalize-url:优化和缩短 URL。
  • postcss-minify-font-values:最小化字体属性值。
  • postcss-minify-gradients:最小化渐变表示。
  • postcss-minify-params:最小化 @ 规则的参数。
  • postcss-minify-selectors:最小化选择器。
  • postcss-normalize-charset:确保只有一个有效的字符集 @ 规则。
  • postcss-normalize-display-values:规范化 display 属性值。
  • postcss-normalize-positions:规范化背景位置属性。
  • postcss-normalize-repeat-style:规范化背景重复样式。
  • postcss-normalize-string:规范化引号。
  • postcss-normalize-timing-functions:规范化时间函数。
  • postcss-normalize-unicode:规范化 unicode-range 描述符。
  • postcss-normalize-whitespace:规范化空白字符。
  • postcss-ordered-values:规范化属性值的顺序。
  • postcss-reduce-initial:将初始值替换为更短的等效值。
  • postcss-reduce-transforms:减少变换属性中的冗余值。
  • postcss-svgo:优化和压缩内联 SVG。
  • postcss-unique-selectors:删除重复的选择器。
  • postcss-zindex:重新计算 z-index 值,以减小文件大小。
module.exports = {
plugins: [
require("cssnano")({
preset: [
'default',
{
discardComments: false,
discardEmpty: false
}
]
})
],
};

stylelint

stylelint可以规范CSS代码,能够将CSS代码统一风格。

yarn add stylelint stylelint-config-standard -D
  • stylelint:做CSS代码风格校验,但是不提供规则。
  • stylelint-config-standardstylelint的一套校验规则,并且是一套标准规则。

配置时要注意顺序:

module.exports = {
plugins: [
require("stylelint")({
fix: true
}),
require("autoprefixer"),
require("cssnano")
],
};

在项目的根目录下面创建一个.stylelintrc,这个文件可以指定具体规则

{
"extends": "stylelint-config-standard",
"rules": {
"comment-empty-line-before": null
}
}

postcss-preset-env

postcss-preset-env的主要作用是:

  • 让开发者可以使用最新的的CSS语法,如:CSS Grid、CSS Variables等。
  • 自动为CSS代码添加浏览器厂商前缀,如:-webkit--moz-等。
  • 根据浏览器兼容性需求,将CSS代码转换为旧版浏览器兼容的语法。
  • 优化CSS代码,如:合并规则、删除重复的代码等。
yarn add postcss-preset-env -D

该插件提供了一个配置选项:

  • stage:设置要使用的CSS特性的阶段,默认值为2(0-4)。数字越小,包含的CSS草案特性越多,但稳定性可能较低。
  • browsers:设置目标浏览器范围,如:last 2 versions> 1%
  • autoprefixer:设置自动添加浏览器厂商前缀的配置。
  • preserve:是否保留原始CSS代码,默认为false。如果设置为true,则会在转换后的代码后面保留原始代码,以便新浏览器优先使用新语法。
module.exports = {
plugins: [
require('postcss-preset-env')({
stage: 2,
autoprefixer: {
grid: true
},
browsers: 'last 10 versions'
})
]
}

postcss-import

postcss-import主要用于处理CSS文件中@import规则。在原生的CSS中,@import可以引入其他的CSS文件,但是在解析CSS文件时,发现有@import就会发送HTTP请求去获取对应的CSS文件。

postcss-import可将多个CSS文件合并为一个文件,减少了浏览器对@import规则的额外请求。

yarn add postcss-import -D

配置项

  • path:设置查找CSS文件的路径,默认为当前文件夹。
  • plugins:指定在处理被@import引入的CSS文件时使用的其他PostCSS插件。这些插件将在postcss-import合并文件之前对被引入的文件进行处理,之后再进行文件的合并。
module.exports = {
plugins: [
require("postcss-import")({
path: ["src/css"],
plugins: [
postcssNested(),
],
}),
],
};

purgecss

purgecss是一个用于移除没有使用到的CSS样式的工具,相当于是CSS版本的Tree Shaking,它会找到文件中实际使用的CSS类名,并且移除没有使用到的样式,有效的减少CSS文件的大小,提升传输速度。

yarn add @fullhuman/postcss-purgecss -D
module.exports = {
plugins: [
require("postcss-import")({
path: ["src/css"]
}),
require("postcss-preset-env")({
stage: 2,
}),
require("@fullhuman/postcss-purgecss")({
content: ['./src/**/*.html'],
safelist: [/^active-/],
})
],
};
  • content:表示具体的参照文件。
  • safelist:可指定一个字符串或正则表达式,表示满足该配置的CSS样式规则始终保留,即使在参照文件中没有使用到也需要保留。

自定义插件

PostCSS支持自定义插件,开发者可以按需求定制自己的插件。

自定义插件模版如下:

module.exports = (opts = {}) => {
// Plugin creator to check options or prepare caches
return {
postcssPlugin: 'PLUGIN NAME'
// Plugin listeners
}
}
module.exports.postcss = true

在插件里面添加一组监听器:

  • Root: 树的顶层节点,表示CSS文件。
  • AtRule: 以@开头的声明语句,例如@charset "UTF-8"@media (screen) {}
  • Rule: 带有声明的选择器,例如input, button {}
  • Declaration: 键值对,例如color: black;
  • Comment: 独立的注释。位于选择器、at-rule参数和值中的注释会存储在节点的raws属性中。

示例,编写一个插件,能够将CSS代码中所有的颜色统一转为十六进制:

const Color = require('color')
const colorRegex = /(^color)|(^background(-color)?)/

module.exports = function (opt = {}) {
return {
postcssPlugin: 'convertColorsToHex',
Declaration(decl) {
if (colorRegex.test(decl.prop)) {
try {
const color = Color(decl.value)
const hex = color.hex()
decl.value = hex
} catch (err) {
console.error(
`[convertColorsToHex] Error processing ${decl.prop}: ${err.message}`
)
}
}
}
}
}
module.exports.postcss = true

抽象语法树

抽象语法树是将源代码抽象成一种更高阶别的表示方式,只关注代码的结构和语法,会去忽略空格、换行、制表符之类的表达细节。

  1. 易于操作和遍历:可以更方便地进行操作和遍历。AST 中的每个节点都有确定的类型和结构,这使得插件作者可以轻松地定位和修改特定类型的节点,而无需解析和操作原始 CSS 文本。
  2. 易于扩展:使用 AST 可以轻松地支持新的 CSS 语法和特性。只需在 AST 中添加相应的节点类型和规则,就可以在插件中处理新的语法结构,而无需对整个解析器进行重大改动。
  3. 提高性能:将 CSS 代码转换为 AST 后,可以对整个树进行一次遍历,同时应用多个插件的变换操作。这样可以减少重复解析和操作 CSS 文本的开销,从而提高处理性能。
  4. 代码重用和模块化:由于 AST 的结构化特性,插件开发者可以在多个插件之间重用和共享操作 AST 的代码。这有助于降低插件间的冗余,并提高代码的模块化程度。
  5. 易于调试和错误处理:AST 中的每个节点都包含有关其源代码位置的元信息。这使得插件可以在出现错误时提供更具体的错误信息和上下文,从而帮助开发者快速定位和解决问题。
  6. 使用抽象语法书的著名项目:BabelESlintPostCSS
var a = 42;
var b = 5;
function addA(d) {
return a + d;
}
var c = addA(2) + b;

对于编译器或者解释器来讲,上面的代码它们并不能够理解。对于编译器或者解释器来讲,无非就是一段字符串而已:

'var a = 42;var b = 5;function addA(d) {return a + d;}var c = addA(2) + b;'

要执行这个代码,编译器或解释器首先第一步就是要分析出来这个字符串里面哪些是关键字,哪些是标志符,哪些是运算符,之后会形成一个一个的token,组成一个树形结构。

例如上面的代码,最终就会形成各种各样的token(不可再拆分、最小的单位):

Keyword(var) Identifier(a) Punctuator(=) Numeric(42) Punctuator(;) Keyword(var) 
Identifier(b) Punctuator(=) Numeric(5) Punctuator(;) Keyword(function)
Identifier(addA) Punctuator(() Identifier(d) Punctuator()) Punctuator({)
Keyword(return) Identifier(a) Punctuator(+) Identifier(d) Punctuator(;)
Punctuator(}) Keyword(var) Identifier(c) Punctuator(=) Identifier(addA)
Punctuator(() Numeric(2) Punctuator()) Punctuator(+) Identifier(b) Punctuator(;)