PostCSS 原理与工程实践
PostCSS 是前端工程化里处理 CSS 的重要工具。它本质不是新的样式语言,而是一套基于抽象语法树转换 CSS 的 JavaScript 工具平台。
PostCSS 是什么?
PostCSS 是一套利用 JavaScript 工具与插件转换 CSS 代码的工具平台。
PostCSS 不等同于 Sass、Less 这类 CSS 预处理器,它更接近 webpack,本身不提供样式语法扩展,依靠丰富插件生态完成 CSS 的编译、语法检查、自动补前缀等各类处理,由 Evil Martians 团队开发维护。
PostCSS 的核心工作流程十分清晰:
- 将 CSS 源码解析为抽象语法树(Abstract Syntax Tree,AST),把 CSS 文本转为 JavaScript 可操作的数据结构;
- 将 AST 交给各个插件执行修改操作;
- 将处理完成的 AST 重新转回 CSS 文本输出。
插件可以基于 AST 完成非常多能力:实现变量、混入(mixin)、自动补充浏览器厂商前缀、把未来标准的 CSS 语法转译为兼容现有浏览器的样式代码。PostCSS 的核心能力来自它的插件体系,社区已经拥有两百多个功能不同的插件,开发者也可以按需编写自定义插件。
PostCSS 本体只承担两件核心工作:
- 把 CSS 解析成 JavaScript 可操作的 AST;
- 调度插件处理 AST,生成最终 CSS 结果。
因此不能简单将 PostCSS 归类为 CSS 预处理工具或者后处理工具,它同时可以完成传统意义上预处理、后处理两类工作。
为什么选择 PostCSS?
在前端工程化体系中,Sass、Less、Stylus 这类 CSS 预处理器被广泛使用。预处理器提供原生 CSS 不具备的高级语法,以此提升样式代码的可读性和可维护性。
社区主流预处理器如下:
- Sass:2007 年诞生,历史最久、生态成熟,最初依赖 Ruby,后续演化出完全兼容原生 CSS 的 SCSS 语法。
- Less:2009 年诞生,语法贴近原生 CSS,上手门槛低,Bootstrap 底层便基于 Less,可编程能力弱于 Sass。
- Stylus:2010 年出自 Node.js 社区,语法灵活,适合构建动态 CSS。
PostCSS 的核心优势:
- 模块化按需选用能力:不需要全套语法,只安装项目实际需要的插件;
- 庞大的插件生态:两百多款社区插件覆盖绝大多数 CSS 处理场景;
- 性能优异,官方基准测试显示处理速度可达传统预处理器的 3 倍以上;
- 支持自定义插件开发,可以根据业务场景编写专属 CSS 转换逻辑;
- 兼容原生 CSS,可以和 Less、Sass 协同工作,不需要强制切换样式语法;
- 和主流构建工具无缝集成,例如 webpack、gulp、CodePen;
- 完善的 Source Map 支持,便于调试编译之后的样式代码。
一句话总结 PostCSS:传统 CSS 预处理器可以实现的效果,PostCSS 借助插件大多都可以完成,并且更加灵活可控。
PostCSS 的使用
PostCSS 的语法扩展全部来自插件,你可以按需安装嵌套、变量、自动前缀等能力,不必学习全新的样式语言,可以直接书写标准 CSS。
插件资源可以查阅官方中文插件列表,或者使用 postcss.parts 进行检索,也可以阅读官方文档开发自定义插件。
安装与配置方式
常见配置方式,优先级从高到低排列:
webpack.config.jspostcss.config.js、.postcssrc.js.postcssrcpackage.json的postcss字段
在 webpack.config.js 的 postcss-loader 选项内直接配置:
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: (loader) => [
require('postcss-import')({ root: loader.resourcePath }),
require('postcss-preset-env')(),
require('cssnano')()
]
}
}
]
}
独立配置文件 postcss.config.js、.postcssrc.js(推荐):
module.exports = ({ file, options, env }) => ({
parser: file.extname === '.sss' ? 'sugarss' : false,
plugins: {
'autoprefixer': {},
'postcss-import': { root: file.dirname },
'postcss-preset-env': options['postcss-preset-env'] ? options['postcss-preset-env'] : false,
'cssnano': env === 'production' ? options.cssnano : false
}
})
JSON 格式配置文件 .postcssrc:
{
"plugins": {
"postcss-plugin": {}
}
}
写在 package.json 的 postcss 字段(优先级最低):
{
"postcss": {
"plugins": {
"postcss-plugin": {}
}
}
}
基于 webpack 搭建最小测试项目
项目借助 postcss-loader 在 webpack 流程中接入 PostCSS。以下示例基于 webpack 4 与 postcss-loader 3。
安装依赖:
npm i webpack@4 webpack-cli@3 -D
npm i css-loader@4 postcss-loader@3 mini-css-extract-plugin@0.10 -D
项目目录结构:
- build
- webpack.postcss.config.js
- src
- index.css
- index.js
- package.json
- postcss.config.js
webpack 配置文件 build/webpack.postcss.config.js:
const Path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const webpackConfig = {
mode: 'production',
entry: './src/index.js',
output: {
path: Path.resolve(__dirname, '../dist'),
filename: 'index.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'index.css',
chunkFilename: 'chunkIndex.css',
})
]
}
module.exports = webpackConfig
注意:MiniCssExtractPlugin 不能与 style-loader 同时使用。用 CLI(webpack --config)即可;若改用 Node API 调用 webpack(config, callback),必须传入回调并处理 err,空回调会把编译失败吞掉。
入口 src/index.js:
import './index.css'
样式文件 src/index.css:
.hello {
box-sizing: border-box;
}
postcss 配置 postcss.config.js:
module.exports = ({ file, options, env }) => ({
plugins: {}
})
在 package.json 添加执行脚本:
{
"scripts": {
"postcss": "webpack --config build/webpack.postcss.config.js"
}
}
在项目根目录,在终端执行命令:
npm run postcss
常用插件
Autoprefixer
Autoprefixer 根据 Can I Use 的浏览器兼容数据,自动为 CSS 属性补充厂商前缀。
npm i autoprefixer -D
目标浏览器范围依靠 Browserslist 配置,支持三种配置位置:
postcss.config.js内overrideBrowserslist;package.json的browserslist;- 项目根目录
.browserslistrc。
配置 postcss.config.js:
module.exports = ({ file }) => ({
plugins: {
"autoprefixer": {
"overrideBrowserslist": [
"> 0.1%",
"last 2 versions",
"Android >= 3.2",
"Firefox >= 20",
"iOS >= 7",
"chrome > 20"
]
}
}
})
输入:
.autoprefixer {
box-sizing: border-box;
}
输出:
.autoprefixer {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
postcss-nesting、postcss-nested
两个插件都实现 CSS 嵌套语法,但遵循两套不同规范:
postcss-nesting:遵循 W3C 标准嵌套选择器,每一层嵌套必须显式写&;postcss-nested:模仿 Sass 的嵌套写法,大部分场景可以省略&。
推荐优先使用 postcss-nesting,贴近标准草案。
npm i postcss-nesting -D
# npm i postcss-nested -D
配置 postcss.config.js:
module.exports = ({ file }) => ({
plugins: {
"postcss-nesting": {}
}
})
输入:
.postcss_nesting {
& sub_class {
width: 100%;
}
& a {
color: red;
}
}
输出:
.postcss_nesting sub_class {
width: 100%;
}
.postcss_nesting a {
color: red;
}
postcss-import
允许 CSS 文件中使用 @import 将外部样式文件内联合并,避免浏览器多次请求样式文件。
npm i postcss-import -D
配置 postcss.config.js:
module.exports = ({ file }) => ({
plugins: {
"postcss-import": {}
}
})
@import './import.css';
编译后会把 import.css 的内容直接合并进输出 CSS。
postcss-preset-env
postcss-preset-env 相当于 CSS 的 Babel,基于 cssdb,将现代 CSS 语法转译为兼容旧浏览器的代码,会根据目标浏览器自动启用对应语法转换。
备注:该插件内置 Autoprefixer、postcss-nesting 等能力,项目开启它之后一般无需重复引入上述插件。
npm i postcss-preset-env -D
配置 postcss.config.js:
module.exports = {
plugins: {
"postcss-preset-env": {
features: {
"custom-properties": {
preserve: false,
variables: {}
},
"nesting-rules": true
}
}
}
}
cssnano
生产环境 CSS 压缩优化插件,不止简单去除空格换行,还会做属性合并、规则合并等多项优化,用来减小最终 CSS 体积。
npm i cssnano -D
配置 postcss.config.js:
module.exports = {
plugins: {
"cssnano": {
preset: ["default", {
// 禁止重命名自定义动画 keyframes 名称
reduceIdents: false,
// 关闭 z-index 值的自动重排优化
zindex: false
}]
}
}
}
postcss-apply
借助 @apply,可以复用定义在 CSS 自定义属性集内的一组样式。
注意:该语法不属于正式 Web 标准,浏览器不再计划原生支持该特性,仅作为编译期能力使用。需要搭配 postcss-import 一起工作。
npm i postcss-apply -D
配置 postcss.config.js:
module.exports = {
plugins: {
"postcss-import": {},
"postcss-apply": {}
}
}
/* apply.css */
:root {
--no-wrap: {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
/* index.css */
@import './apply.css';
.apply {
color: green;
@apply --no-wrap;
}
输出:
.apply {
color: green;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
postcss-mixins
提供混入(mixin)能力,支持定义可复用样式片段并传入参数。
npm i postcss-mixins -D
配置 postcss.config.js:
module.exports = ({ file }) => ({
plugins: {
"postcss-mixins": {},
}
})
输入:
/* index.css */
@define-mixin icon $name, $color: blue {
.icon.is-$(name) {
color: $color;
}
.icon.is-$(name):hover {
color: white;
background: $color;
}
}
@mixin icon twitter {
background: url(twt.png);
}
输出:
.icon.is-twitter {
color: blue;
}
.icon.is-twitter:hover {
color: white;
background: blue;
}
.icon.is-twitter {
background: url(twt.png);
}
postcss-px2rem-exclude
将 px 单位自动转换为 rem,同时支持配置排除目录,不转换某些文件。
npm i postcss-px2rem-exclude -D
配置 postcss.config.js:
module.exports = {
"plugins": {
"postcss-px2rem-exclude": {
remUnit: 75,
exclude: /node_modules|folder_name/i
}
}
}
输入:
.px2rem {
width: 100px;
font-size: 30px;
}
输出:
.px2rem {
width: 1.333333rem;
font-size: 0.4rem;
}