跳到主要内容

PostCSS 与 Autoprefixer

问题

PostCSS 是什么?它和 Sass/Less 有什么区别?Autoprefixer 是如何工作的?如何编写一个自定义 PostCSS 插件?

答案

PostCSS 是一个用 JavaScript 转换 CSS 的工具。很多人误以为它是一个预处理器(类似 Sass/Less),但实际上 PostCSS 本身不做任何事情——它只是把 CSS 解析成抽象语法树(AST),然后交给插件去处理,最后再将 AST 转回 CSS。可以把它理解为 "CSS 界的 Babel":Babel 通过插件转换 JavaScript,PostCSS 通过插件转换 CSS。

一句话总结

PostCSS = CSS 解析器 + 插件系统 + 代码生成器,功能完全取决于你使用了哪些插件。

核心原理

PostCSS 的工作流程可以概括为三个阶段:解析(Parse)转换(Transform)生成(Stringify)

AST 节点类型

PostCSS 将 CSS 解析为以下几种核心节点类型:

节点类型说明CSS 示例
Root根节点,代表整个 CSS 文件整个样式表
AtRule@ 开头的规则@media@import@keyframes
Rule选择器规则.container { ... }
Declaration属性声明color: red
Comment注释/* 注释 */
PostCSS AST 结构示例
// 原始 CSS
// .container { color: red; font-size: 16px; }

// 解析后的 AST 结构(简化)
const ast = {
type: 'root',
nodes: [
{
type: 'rule',
selector: '.container',
nodes: [
{ type: 'decl', prop: 'color', value: 'red' },
{ type: 'decl', prop: 'font-size', value: '16px' },
],
},
],
};

PostCSS vs Sass/Less

PostCSS 和 Sass/Less 是完全不同的工具,它们可以共存互补,而非二选一。

特性PostCSSSass/Less
本质CSS 转换工具(基于插件)CSS 预处理器
语法标准 CSS(通过插件扩展)自定义语法(.scss/.less)
扩展方式插件(JavaScript)内置功能(mixin、函数等)
功能范围取决于安装的插件,无限可能变量、嵌套、mixin、函数等固定功能
执行时机可在 Sass 编译后运行编译为标准 CSS
生态丰富的插件生态内置功能完备
性能通常更快(按需加载插件)编译大型文件可能较慢
学习成本低(标准 CSS + 插件配置)中(需学习预处理器语法)
实际项目中的关系

很多项目同时使用 Sass 和 PostCSS:用 Sass 编写样式(利用嵌套、mixin 等),编译后再用 PostCSS 处理(自动加前缀、压缩等)。工作流为:.scss → Sass 编译 → .css → PostCSS 插件处理 → 最终 .css

常用插件

Autoprefixer

Autoprefixer 是 PostCSS 最知名的插件,用于自动添加浏览器厂商前缀(如 -webkit--moz--ms-)。

输入 CSS
.container {
display: flex;
user-select: none;
backdrop-filter: blur(10px);
}
Autoprefixer 输出
.container {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
核心优势

你不需要手动记忆哪些属性需要前缀,Autoprefixer 会根据 Can I Use 数据库和你指定的目标浏览器自动判断。

postcss-preset-env

postcss-preset-env 允许你使用未来的 CSS 特性(如原生嵌套、自定义媒体查询等),它会根据目标浏览器自动进行降级编译。

使用未来 CSS 特性
/* 原生 CSS 嵌套(Stage 3) */
.card {
background: oklch(70% 0.1 200);

& .title {
color: oklch(30% 0.1 200);
}

&:hover {
background: oklch(75% 0.1 200);
}
}

/* 自定义媒体查询(Stage 2) */
@custom-media --mobile (max-width: 768px);

@media (--mobile) {
.card {
padding: 1rem;
}
}

cssnano

cssnano 是一个 CSS 压缩工具,用于生产环境压缩 CSS 文件大小。

压缩前
.container {
margin: 10px 20px 10px 20px;
color: #ff0000;
font-weight: bold;
/* 这是一条注释 */
}
压缩后
.container{margin:10px 20px;color:red;font-weight:700}

cssnano 的优化包括:去除注释和空白、合并简写属性、优化颜色值、移除重复规则等。

postcss-import

postcss-import@import 规则内联合并,避免运行时多次网络请求。

合并前:多个文件
/* main.css */
@import './reset.css';
@import './variables.css';
@import './components/button.css';

.app { color: #333; }
合并后:单个文件
/* reset.css 的内容被内联 */
* { margin: 0; padding: 0; box-sizing: border-box; }
/* variables.css 的内容被内联 */
:root { --primary: #1890ff; }
/* components/button.css 的内容被内联 */
.btn { padding: 8px 16px; }

.app { color: #333; }

postcss-modules

postcss-modules 实现 CSS Modules,通过将类名哈希化来实现样式隔离,避免全局命名冲突。

styles.module.css — 输入
.title {
font-size: 24px;
color: #333;
}

.active {
color: #1890ff;
}
编译后输出
._title_1a2b3 {
font-size: 24px;
color: #333;
}

._active_4d5e6 {
color: #1890ff;
}
在组件中使用
import styles from './styles.module.css';

// styles.title === '_title_1a2b3'
// styles.active === '_active_4d5e6'
const element = document.createElement('div');
element.className = styles.title;

Tailwind CSS(作为 PostCSS 插件)

Tailwind CSS 本质上是一个 PostCSS 插件,它扫描模板文件中的类名,按需生成对应的原子化 CSS。

postcss.config.js
module.exports = {
plugins: {
tailwindcss: {}, // Tailwind 作为 PostCSS 插件
autoprefixer: {}, // 自动添加前缀
},
};
注意

Tailwind CSS v4 已改用 Vite 插件和独立 CLI 作为主要使用方式,PostCSS 插件仅作为向后兼容选项。新项目推荐使用官方推荐的集成方式。

Autoprefixer 详解

工作原理

Autoprefixer 的核心流程如下:

  1. 解析 CSS:PostCSS 将 CSS 解析为 AST
  2. 查询 Can I Use:对每个 CSS 属性/值,查询 Can I Use 数据库获取浏览器兼容性数据
  3. 匹配目标浏览器:根据项目的 browserslist 配置,确定哪些浏览器版本需要前缀
  4. 智能添加/移除前缀:只添加目标浏览器需要的前缀,同时移除已不需要的旧前缀

browserslist 配置

browserslist 定义了项目的目标浏览器范围,Autoprefixer 和 postcss-preset-env 等工具都依赖它。

package.json — browserslist 配置
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

也可以使用独立的配置文件:

.browserslistrc
# 全球使用率超过 1% 的浏览器
> 1%
# 每个浏览器的最近 2 个版本
last 2 versions
# 排除已停止更新的浏览器
not dead
# 排除 IE 11
not ie 11

常用查询条件:

查询条件说明
> 1%全球使用率超过 1%
last 2 versions每个浏览器的最近 2 个版本
not dead排除已停止更新超过 24 个月的浏览器
not ie 11排除 IE 11
defaults等价于 > 0.5%, last 2 versions, Firefox ESR, not dead
supports es6-module支持 ES Module 的浏览器
maintained node versions所有仍在维护的 Node.js 版本
调试 browserslist

使用命令行工具查看目标浏览器列表:

npx browserslist

这会输出当前配置匹配到的所有浏览器版本,帮助你验证配置是否符合预期。

配置方式

postcss.config.js

最常用的配置方式是在项目根目录创建 postcss.config.js(或 .cjs/.mjs/.ts):

postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'postcss-preset-env': {
stage: 2,
features: {
'nesting-rules': true,
'custom-media-queries': true,
},
},
autoprefixer: {},
// 生产环境启用压缩
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
};
插件顺序很重要

插件按声明顺序依次执行。通常推荐的顺序为:

  1. postcss-import(先合并文件)
  2. postcss-preset-env / tailwindcss(语法转换)
  3. autoprefixer(添加前缀)
  4. cssnano(最后压缩)

与 Webpack 集成

在 Webpack 中使用 PostCSS 需要 postcss-loader

npm install postcss postcss-loader autoprefixer postcss-preset-env --save-dev
webpack.config.ts
import type { Configuration } from 'webpack';

const config: Configuration = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
'postcss-preset-env',
'autoprefixer',
],
},
},
},
],
},
],
},
};

export default config;
Loader 执行顺序

Webpack Loader 从右向左(从下往上)执行:postcss-loadercss-loaderstyle-loader。PostCSS 在最先处理 CSS,然后交给 css-loader 解析模块依赖,最后 style-loader 将样式注入 DOM。

与 Vite 集成

Vite 内置了 PostCSS 支持,无需额外安装 loader,只需安装插件并创建配置文件即可:

npm install autoprefixer postcss-preset-env --save-dev
vite.config.ts
import { defineConfig } from 'vite';
import autoprefixer from 'autoprefixer';
import postcssPresetEnv from 'postcss-preset-env';

export default defineConfig({
css: {
postcss: {
plugins: [
postcssPresetEnv({
stage: 2,
features: { 'nesting-rules': true },
}),
autoprefixer(),
],
},
},
});

当然,Vite 也会自动读取项目根目录的 postcss.config.js,大部分情况下直接创建配置文件即可,不需要在 vite.config.ts 中重复配置。

手写一个 PostCSS 插件

PostCSS 插件是一个函数,接收插件选项,返回一个包含 postcssPlugin 名称和各种访问者方法的对象。下面以一个px 转 rem 插件为例:

postcss-px-to-rem.ts — 手写 PostCSS 插件
import type { PluginCreator, Declaration } from 'postcss';

interface PxToRemOptions {
/** 根元素 font-size,默认 16 */
rootValue?: number;
/** 精度(小数位数),默认 5 */
precision?: number;
/** 需要忽略的属性列表 */
exclude?: string[];
}

const pxToRem: PluginCreator<PxToRemOptions> = (opts = {}) => {
const rootValue = opts.rootValue ?? 16;
const precision = opts.precision ?? 5;
const exclude = opts.exclude ?? [];

// 匹配 px 值的正则(如 16px、1.5px,但排除 0px)
const pxRegex = /(\d*\.?\d+)px/gi;

function convertPxToRem(value: string): string {
return value.replace(pxRegex, (match, pxValue) => {
const px = parseFloat(pxValue);
if (px === 0) return '0';
const rem = (px / rootValue).toFixed(precision);
// 去除末尾多余的 0
return `${parseFloat(rem)}rem`;
});
}

return {
postcssPlugin: 'postcss-px-to-rem',

// 访问每个 CSS 声明节点
Declaration(decl: Declaration) {
// 跳过被排除的属性
if (exclude.includes(decl.prop)) return;

// 只处理包含 px 的值
if (!pxRegex.test(decl.value)) return;

// 重置正则的 lastIndex(因为使用了 g 标志)
pxRegex.lastIndex = 0;

// 转换值
decl.value = convertPxToRem(decl.value);
},
};
};

pxToRem.postcss = true; // 标记这是一个 PostCSS 插件

export default pxToRem;

使用这个插件:

postcss.config.js
const pxToRem = require('./postcss-px-to-rem');

module.exports = {
plugins: [
pxToRem({ rootValue: 16, precision: 4, exclude: ['border'] }),
require('autoprefixer'),
],
};

转换效果:

输入
.container {
font-size: 16px;
padding: 8px 16px;
margin: 24px;
border: 1px solid #ccc; /* border 被排除,不会转换 */
}
输出
.container {
font-size: 1rem;
padding: 0.5rem 1rem;
margin: 1.5rem;
border: 1px solid #ccc;
}

插件 API 速查

PostCSS 插件可以监听以下访问者方法:

方法触发时机常见用途
Once(root)处理开始时,只调用一次全局分析、注入样式
Root(root)每个根节点全局处理
Rule(rule)每个选择器规则修改选择器
AtRule(atRule)每个 @ 规则处理 @media@import
Declaration(decl)每个属性声明修改属性值(最常用)
Comment(comment)每个注释删除/修改注释
OnceExit(root)处理结束时清理、统计

postcss-preset-env vs 手动选择插件

对比项postcss-preset-env手动选择插件
便捷性一个包含多个特性,开箱即用需要逐个安装和配置
灵活性通过 stagefeatures 控制完全自定义
包体积包含所有 Stage 特性的代码只安装需要的插件
维护成本升级一个包即可需要分别维护各插件版本
适用场景大多数项目,快速上手对包体积敏感或有特殊需求

postcss-preset-env 使用 Stage 来划分 CSS 特性的稳定程度:

Stage稳定性说明示例
Stage 0非正式草案高度实验性
Stage 1实验性提案阶段
Stage 2默认启用草案规范嵌套规则、自定义媒体查询
Stage 3候选推荐基本稳定颜色函数 oklch()
Stage 4标准浏览器已实现
postcss.config.js — 按需配置
module.exports = {
plugins: {
'postcss-preset-env': {
stage: 2, // 启用 Stage 2 及以上的特性
features: {
'nesting-rules': true, // 确保启用嵌套
'custom-media-queries': true, // 自定义媒体查询
'color-function': false, // 禁用某个特性
},
autoprefixer: { grid: 'autoplace' }, // 内置 Autoprefixer 配置
},
},
};
注意

postcss-preset-env 内置了 Autoprefixer。如果你使用了 postcss-preset-env,不需要再单独添加 autoprefixer 插件,否则前缀可能被重复添加。可以通过 autoprefixer 选项在 postcss-preset-env 内部配置它。


常见面试问题

Q1: PostCSS 是什么?和 Sass/Less 有什么区别?

答案

PostCSS 是一个用 JavaScript 转换 CSS 的工具,它本身不提供任何转换功能,所有功能都通过插件实现。它的工作流程是:将 CSS 解析为 AST → 插件遍历和修改 AST → 将 AST 序列化回 CSS。因此,PostCSS 经常被称为 "CSS 界的 Babel"

与 Sass/Less 的核心区别:

维度PostCSSSass/Less
定位CSS 转换工具平台CSS 预处理语言
语法标准 CSS 语法自定义语法(.scss/.less
能力来源外部插件(按需安装)语言内置(变量、mixin、函数等)
可扩展性高(任何人可写插件)低(受限于语言设计)
典型用途自动前缀、未来 CSS 降级、压缩变量、嵌套、模块化编写样式

在实际项目中,两者通常配合使用而非互斥:Sass 负责编写阶段的语法增强,PostCSS 负责编译后的 CSS 优化和兼容处理。

典型工作流配置
// Webpack 中 Sass + PostCSS 的配置
const cssRules = {
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader', // 第二步:PostCSS 处理(加前缀、压缩等)
'sass-loader', // 第一步:Sass 编译为标准 CSS
],
};

Q2: Autoprefixer 的工作原理是什么?

答案

Autoprefixer 的工作原理分为三步:

  1. 解析 CSS:借助 PostCSS 将 CSS 解析为 AST,遍历所有属性声明
  2. 查询兼容性数据:对每个 CSS 属性,查询 Can I Use 数据库,获取各浏览器对该属性的支持情况(是否需要前缀、从哪个版本开始原生支持)
  3. 匹配目标浏览器:根据项目的 browserslist 配置,判断目标浏览器中是否有需要前缀的版本,如果有则添加对应前缀,如果所有目标浏览器都已原生支持则不添加

关键特性:

  • 智能添加:只添加目标浏览器真正需要的前缀,不会盲目添加所有前缀
  • 自动移除:如果检测到代码中有已经不需要的旧前缀,会自动移除
  • 基于数据驱动:依赖 Can I Use 的实时数据,而非硬编码规则,更新 caniuse-lite 数据库就能获取最新兼容性信息
browserslist 如何影响输出
// 假设 browserslist 配置为 "last 2 Chrome versions"
// Chrome 最近两个版本已原生支持 flexbox

// 输入
// .box { display: flex; }

// 输出(不需要前缀,因为目标浏览器都支持)
// .box { display: flex; }

// ---

// 假设 browserslist 配置为 "> 0.1%"(覆盖更多旧浏览器)

// 输入
// .box { display: flex; }

// 输出(旧浏览器需要前缀)
// .box {
// display: -webkit-box;
// display: -ms-flexbox;
// display: flex;
// }

更新 Can I Use 数据库:

npm install caniuse-lite --save-dev

Q3: 如何编写一个 PostCSS 插件?

答案

编写 PostCSS 插件的核心步骤:

  1. 创建插件函数:导出一个函数,接收配置选项,返回包含 postcssPlugin 名称和访问者方法的对象
  2. 使用访问者模式:通过 DeclarationRuleAtRule 等方法遍历和修改 AST 节点
  3. 标记为插件:设置 函数名.postcss = true
完整的 PostCSS 插件模板
import type { PluginCreator, Root, Declaration, Rule } from 'postcss';

interface MyPluginOptions {
/** 选项示例 */
enable?: boolean;
}

const myPlugin: PluginCreator<MyPluginOptions> = (opts = {}) => {
const { enable = true } = opts;

return {
postcssPlugin: 'postcss-my-plugin', // 必须:插件名称

// 处理开始时调用一次
Once(root: Root) {
// 可以在这里做全局分析
console.log('开始处理 CSS 文件');
},

// 每个属性声明都会触发
Declaration(decl: Declaration) {
if (!enable) return;

// 示例:将所有 color: red 改为 color: blue
if (decl.prop === 'color' && decl.value === 'red') {
decl.value = 'blue';
}
},

// 每个选择器规则都会触发
Rule(rule: Rule) {
// 示例:给所有选择器添加前缀
// rule.selector = `.prefix ${rule.selector}`;
},

// 处理结束时调用一次
OnceExit(root: Root) {
console.log('CSS 处理完成');
},
};
};

myPlugin.postcss = true; // 必须:标记为 PostCSS 插件

export default myPlugin;

常用的 AST 操作方法:

AST 节点操作 API
import type { Declaration, Rule, Root } from 'postcss';

// 修改属性值
function handleDecl(decl: Declaration): void {
decl.value = 'new-value'; // 修改值
decl.prop = 'new-prop'; // 修改属性名
decl.remove(); // 删除该声明
decl.replaceWith(decl.clone({ value: 'new' })); // 替换节点
}

// 修改规则
function handleRule(rule: Rule): void {
rule.selector = '.new-selector'; // 修改选择器
rule.append({ prop: 'color', value: 'red' }); // 添加新声明
rule.prepend({ prop: 'display', value: 'flex' }); // 在最前面添加
}

// 修改根节点
function handleRoot(root: Root): void {
root.walkDecls('color', (decl) => { // 遍历所有 color 声明
decl.value = 'blue';
});
root.walkRules(/^\.btn/, (rule) => { // 遍历匹配的规则
// 处理所有 .btn 开头的规则
});
}
开发建议
  • 使用 AST Explorer(选择 CSS / postcss)可以直观查看 CSS 的 AST 结构,帮助理解节点关系
  • 插件应该是幂等的:多次运行产生相同结果
  • 优先使用具体的访问者方法(如 Declaration)而非 Once 中手动 walk,性能更好

相关链接