PostCSS

CSS 的"Babel"——用插件流水线把现代 CSS 变成能跑的 CSS

Posted by chanweiyan on April 29, 2026

一句话理解

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 文件和行号。

踩坑提示

  1. 插件顺序错误postcss-import 必须最先(否则 @import 还没展开,其他插件就处理了残缺文件);autoprefixerpostcss-preset-env 之后(preset-env 已包含 autoprefixer,重复会有冲突)
  2. 重复运行 Autoprefixerpostcss-preset-env 内置了 Autoprefixer,不要再单独加一次,否则会重复处理
  3. Vite 下 postcss.config.jsvite.config.ts 同时配置:两个都存在时 Vite 会合并,可能行为出乎意料,建议选一处配置
  4. @import 不展开:需要 postcss-import 插件;CSS Modules 或 webpack css-loader 自己处理 @import,这时不要重复加插件
  5. 生产环境 SourceMap 泄漏源码:确保 map: false 或不输出 .map 文件到 CDN
  6. postcss-nested vs 原生 CSS 嵌套:2024 年后主流浏览器已原生支持 CSS 嵌套(&),如果不需要兼容旧浏览器可以直接不加插件
  7. 插件版本不兼容:PostCSS 8 插件 API 与 PostCSS 7 不兼容,升级时注意所有插件也要同步升级

小结

  • PostCSS = CSS AST 框架,本身不转换任何东西,能力全来自插件
  • 核心工作流:解析 → 插件操作 AST → 序列化输出
  • 日常用的不多:配好 postcss-preset-env(含 Autoprefixer)+ cssnano 基本就够
  • 想写插件:监听节点类型(DeclarationRuleAtRule…),操作节点的 prop / value / clone / remove
  • Vite / webpack / Next.js 都内置 PostCSS,零配置自动生效
  • 和 Sass/Less 不冲突,可以串联使用

  • https://www.npmjs.com/package/postcss-prefix-selector