一句话理解
PostCSS = 一个把 CSS 解析成 AST、让插件随意操作节点、再序列化回 CSS 的工具链。
它本身不做任何 CSS 转换,所有能力都来自插件。你用的 Autoprefixer、Tailwind CSS、CSS Modules——底层都是 PostCSS 插件。
1
2
3
4
5
6
7
8
9
10
CSS 源码
│
▼
PostCSS 解析器 → AST(节点树)
│
▼
插件 1 → 插件 2 → 插件 N(按顺序操作 AST)
│
▼
序列化 → 输出 CSS + SourceMap
PostCSS vs Sass/Less vs CSS-in-JS
| 维度 | Sass / Less | PostCSS | CSS-in-JS |
|---|---|---|---|
| 本质 | 预处理器(超集语法) | CSS AST 插件框架 | JS 运行时 / 构建时 |
| 运行时机 | 构建时 | 构建时 | 运行时 or 构建时 |
| 扩展方式 | 内置(变量、混入…) | 插件生态(无限扩展) | JS 对象 / 模板字符串 |
| 学习曲线 | 低 | 低(用插件)/ 中(写插件) | 高 |
| 代表使用场景 | 传统多页应用样式 | 现代工具链(Vite/Webpack) | React 组件库 |
| 和原生 CSS 差距 | 超集语法不兼容 | 直接写标准 CSS | 完全脱离 CSS 文件 |
PostCSS 最大的优势:你写的就是未来的标准 CSS,插件帮你把还不被浏览器支持的部分 polyfill / transform 掉。
核心概念:AST 节点类型
1
2
3
4
5
6
Root → 整个 CSS 文件
Rule → 一条选择器规则 (.foo { ... })
Declaration → 一个属性声明 (color: red)
AtRule → @ 规则 (@media, @keyframes, @import)
Rule / Declaration(嵌套其中)
Comment → 注释 (/* ... */)
自己写插件时主要操作这几种节点。
常用插件速览
| 插件 | 作用 |
|---|---|
| Autoprefixer | 自动加 -webkit-、-moz- 等浏览器前缀 |
| postcss-preset-env | 一站式:把草案/新特性降级为兼容写法(含 Autoprefixer) |
| Tailwind CSS | 原子 CSS 框架(PostCSS 插件形式集成) |
| CSS Modules | 给选择器自动加哈希,实现作用域隔离 |
| postcss-import | 把 @import 内联合并成单文件 |
| postcss-nested | 支持嵌套规则(类 Sass 写法) |
| postcss-custom-media | @custom-media 草案支持 |
| cssnano | CSS 压缩 / 最小化 |
| stylelint | CSS lint(也是 PostCSS 插件) |
| postcss-pxtorem | px 自动转 rem(移动端适配) |
安装与配置
1. 安装
1
npm install -D postcss postcss-cli autoprefixer postcss-preset-env
2. 配置文件
推荐用 postcss.config.js(或 .cjs):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// postcss.config.js
module.exports = {
plugins: [
// 数组写法:可精确控制顺序
require("postcss-import"),
require("postcss-nested"),
require("postcss-preset-env")({
stage: 2, // 草案成熟度(0-4),2 是平衡点
features: {
"nesting-rules": true, // 开启原生嵌套(如果没用 postcss-nested)
},
}),
require("autoprefixer"), // 必须在 preset-env 之后
process.env.NODE_ENV === "production" ? require("cssnano") : false,
].filter(Boolean),
};
也支持 postcss.config.ts(需 ts-node),或直接在 vite.config.ts / webpack.config.js 里内联配置。
3. 与 Vite 集成(最常见)
Vite 内置 PostCSS,零配置自动读取 postcss.config.js:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({
css: {
// 也可以直接在这里写,不用单独 postcss.config.js
postcss: {
plugins: [
require("autoprefixer"),
require("postcss-preset-env")({ stage: 2 }),
],
},
},
});
4. 与 webpack 集成
1
2
3
4
5
6
7
8
9
// webpack.config.js(需 postcss-loader)
{
test: /\.css$/,
use: [
"style-loader",
"css-loader", // 处理 @import / url()
"postcss-loader", // 读取 postcss.config.js
],
}
5. CLI 命令行
1
2
3
4
5
# 单文件转换
npx postcss src/style.css -o dist/style.css
# watch 模式
npx postcss src/**/*.css --dir dist --watch
postcss-preset-env 详解
这是最重要的单个插件,相当于 CSS 的 @babel/preset-env:
1
2
3
4
5
6
7
8
9
10
11
12
13
require("postcss-preset-env")({
stage: 2, // 草案成熟度(0 实验性 → 4 已标准化)
browsers: "> 1%, not dead", // 目标浏览器(优先读 .browserslistrc)
features: {
// 强制开启/关闭特定特性
"custom-properties": false, // 不转换 CSS 变量(现代浏览器已原生支持)
"nesting-rules": true, // 开启嵌套
"color-function": true, // color() 函数降级
},
autoprefixer: {
flexbox: "no-2009", // 跳过老版 flexbox 语法
},
});
.browserslistrc 目标浏览器配置
# .browserslistrc
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 11
Autoprefixer 和 preset-env 都自动读取这个文件。
写一个简单的 PostCSS 插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// postcss-rem-fallback.js
// 在每条 rem 声明前插入等效的 px 声明,作为旧浏览器 fallback
const postcss = require("postcss");
/**
* @type {import('postcss').PluginCreator}
*/
module.exports = (opts = { rootValue: 16 }) => {
return {
postcssPlugin: "postcss-rem-fallback", // 插件名称(必填)
// 每遇到一个属性声明就调用
Declaration(decl) {
// 只处理值里包含 rem 的声明
if (!decl.value.includes("rem")) return;
// 把 rem 数值换算成 px
const pxValue = decl.value.replace(
/([\d.]+)rem/g,
(_, num) => `${parseFloat(num) * opts.rootValue}px`,
);
// 在当前节点前插入 px fallback 声明
decl.cloneBefore({ value: pxValue });
},
};
};
module.exports.postcss = true; // 标记为 PostCSS 8 插件
使用:
1
2
3
4
5
6
// postcss.config.js
module.exports = {
plugins: [
require("./postcss-rem-fallback")({ rootValue: 16 }),
],
};
输入:
1
2
3
4
.box {
font-size: 1.5rem;
padding: 1rem 2rem;
}
输出:
1
2
3
4
5
6
.box {
font-size: 24px; /* px fallback */
font-size: 1.5rem;
padding: 16px 32px; /* px fallback */
padding: 1rem 2rem;
}
SourceMap
PostCSS 默认生成 SourceMap,调试时能精确定位到源文件行号:
1
2
3
4
5
6
7
// postcss.config.js
module.exports = {
map: process.env.NODE_ENV !== "production"
? { inline: false } // 开发:外链 .css.map 文件
: false, // 生产:不生成
plugins: [...],
};
Vite / webpack 会自己管理 SourceMap,一般不需要手动配置。
QA: PostCSS 和 Sass 能同时用吗?
💬点击展开/收起
可以,且很常见。典型工具链:
1
2
3
4
5
6
7
8
9
10
.scss 文件
│
▼
sass(编译 SCSS → CSS)
│
▼
PostCSS(Autoprefixer + cssnano 等)
│
▼
最终 CSS
Vite 配置示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
// vite.config.ts
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variables" as *;`,
},
},
postcss: {
plugins: [require("autoprefixer"), require("cssnano")],
},
},
});
Vite 内部流程:先走 sass/less 编译,再走 PostCSS 处理,顺序固定,无需手动串联。
QA: postcss-preset-env 的 stage 怎么选?
💬点击展开/收起
stage 代表 W3C CSS 草案的成熟阶段:
| stage | 状态 | 建议 |
|---|---|---|
| 0 | 非官方想法 | 不建议,极不稳定 |
| 1 | 提案阶段 | 谨慎,API 可能大改 |
| 2 | 草案(推荐) | 主流项目的平衡点 |
| 3 | 候选推荐(浏览器已试验) | 可用,大部分特性已原生支持 |
| 4 | 已成为标准 | 不需要插件转换了 |
推荐 stage: 2:包含嵌套、自定义媒体查询、:is()/:has() 等实用特性,同时不引入过于激进的实验性语法。
如果你的目标浏览器很现代(Chrome/Firefox 最新 2 版),可以设 stage: 3 减少转换开销。
QA: CSS Modules 和 PostCSS 是什么关系?
💬点击展开/收起
CSS Modules 是一个规范 + 工具链实现,不是 PostCSS 插件。但实现 CSS Modules 的工具(css-loader、Vite 内置)内部会用 PostCSS 处理 :local()、:global() 和 composes 等语法。
在 Vite 里启用 CSS Modules 只需把文件命名为 *.module.css:
1
2
3
4
/* Button.module.css */
.btn {
background: blue;
}
1
2
import styles from "./Button.module.css";
// styles.btn → "btn_abc123"(哈希化的类名)
PostCSS 插件和 CSS Modules 可以同时生效:Vite 先过 CSS Modules 转换,再过 PostCSS 插件链(如 Autoprefixer)。
QA: 怎么调试 PostCSS 插件执行后的 CSS?
💬点击展开/收起
方法 1:CLI 直接看输出
1
npx postcss src/style.css --no-map | head -100
方法 2:加临时 debug 插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// postcss.config.js
const debugPlugin = {
postcssPlugin: "debug",
Once(root) {
console.log(root.toResult().css);
},
};
module.exports = {
plugins: [
require("autoprefixer"),
debugPlugin, // 插到想查看的位置之后
],
};
方法 3:浏览器 DevTools + SourceMap
开启 SourceMap 后,Chrome DevTools 的 Sources 面板能直接显示转换前的源 CSS 文件和行号。
踩坑提示
- 插件顺序错误:
postcss-import必须最先(否则@import还没展开,其他插件就处理了残缺文件);autoprefixer在postcss-preset-env之后(preset-env 已包含 autoprefixer,重复会有冲突) - 重复运行 Autoprefixer:
postcss-preset-env内置了 Autoprefixer,不要再单独加一次,否则会重复处理 - Vite 下
postcss.config.js和vite.config.ts同时配置:两个都存在时 Vite 会合并,可能行为出乎意料,建议选一处配置 @import不展开:需要postcss-import插件;CSS Modules 或 webpackcss-loader自己处理@import,这时不要重复加插件- 生产环境 SourceMap 泄漏源码:确保
map: false或不输出.map文件到 CDN postcss-nestedvs 原生 CSS 嵌套:2024 年后主流浏览器已原生支持 CSS 嵌套(&),如果不需要兼容旧浏览器可以直接不加插件- 插件版本不兼容:PostCSS 8 插件 API 与 PostCSS 7 不兼容,升级时注意所有插件也要同步升级
小结
- PostCSS = CSS AST 框架,本身不转换任何东西,能力全来自插件
- 核心工作流:解析 → 插件操作 AST → 序列化输出
- 日常用的不多:配好
postcss-preset-env(含 Autoprefixer)+cssnano基本就够 - 想写插件:监听节点类型(
Declaration、Rule、AtRule…),操作节点的prop/value/clone/remove - Vite / webpack / Next.js 都内置 PostCSS,零配置自动生效
-
和 Sass/Less 不冲突,可以串联使用
- https://www.npmjs.com/package/postcss-prefix-selector