PostCSS 常用配置
大白话解释: PostCSS 就像"CSS 的 Babel"。Babel 把新语法的 JS 转换成旧语法,PostCSS 把新语法的 CSS 转换成旧语法,加上浏览器前缀、压缩等处理。
预处理器 vs 后处理器:
- 预处理器(Less/Sass):写的时候用增强语法,编译成普通 CSS
- 后处理器(PostCSS):编译后的 CSS 再加工处理
PostCSS 能做什么?
- 自动加浏览器前缀:
display: flex自动变成display: -webkit-box等带前缀写法 - CSS 转换:把新的 CSS 语法转换成旧浏览器能识别的
- 压缩:去除空格、注释,减小文件体积
- px 转 vw/rem:移动端适配
什么时候用 PostCSS?
- 需要兼容旧浏览器(自动加前缀)
- 移动端适配(px 转 vw/rem)
- 使用新的 CSS 语法(如嵌套、自定义属性)
PostCSS 是 CSS 后处理器,通过插件转换 CSS 代码。Vite、Webpack 等构建工具都内置了 PostCSS 支持。
什么是 PostCSS
预处理器 vs 后处理器
| 工具 | 类型 | 作用 | 代表 |
|---|---|---|---|
| Less/Sass | 预处理器 | 编写时扩展 CSS 语法 | 变量、嵌套、Mixin |
| PostCSS | 后处理器 | 编译后转换/优化 CSS | 前缀、转换、压缩 |
PostCSS 工作流程
源 CSS → PostCSS 插件链 → 输出 CSS
/* 输入 */
::placeholder {
color: #999;
}
.display-flex {
display: flex;
}
/* 输出(经过 autoprefixer) */
::-moz-placeholder {
color: #999;
}
::-ms-input-placeholder {
color: #999;
}
::placeholder {
color: #999;
}
.display-flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}基本配置
配置文件
js
// postcss.config.js(推荐放在项目根目录)
module.exports = {
plugins: [
require('autoprefixer'), // 自动添加浏览器前缀
require('cssnano') // 生产环境压缩 CSS
]
}
// postcss.config.cjs(.cjs 扩展名强制使用 CommonJS 格式,不受 package.json 的 type 字段影响)
module.exports = {
plugins: [
require('autoprefixer'), // 同上,适用于 package.json 设了 "type": "module" 的项目
require('cssnano')
]
}
// package.json(不想单独建配置文件时可以用这种方式)
{
"postcss": {
"plugins": {
"autoprefixer": {}, // 默认配置
"cssnano": {} // 默认配置
}
}
}Vite 中使用
Vite 内置 PostCSS 支持,只需创建 postcss.config.js 即可自动生效。
js
// postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}Webpack 中使用
js
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
}常用插件
autoprefixer(自动添加浏览器前缀)
bash
npm install -D autoprefixerjs
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}browserslist 配置
json
// package.json
{
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}json
// 或单独的 .browserslistrc 文件
> 1%
last 2 versions
not dead
not ie 11常用 browserslist 查询
> 1% 全球使用率 > 1% 的浏览器
last 2 versions 每个浏览器的最后 2 个版本
not dead 官方不再维护的浏览器
not ie 11 排除 IE 11
defaults browserslist 默认配置
ie 6-8 IE 6-8
chrome >= 50 Chrome 50+输入输出
css
/* 输入 */
::placeholder {
color: #999;
}
.flex {
display: flex;
}
.gradient {
background: linear-gradient(to bottom, #fff, #000);
}
/* 输出 */
::-moz-placeholder {
color: #999;
}
::-ms-input-placeholder {
color: #999;
}
::placeholder {
color: #999;
}
.flex {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
}
.gradient {
background: -webkit-linear-gradient(top, #fff, #000);
background: linear-gradient(to bottom, #fff, #000);
}postcss-px-to-viewport(px 转 vw)
bash
npm install -D postcss-px-to-viewportjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-px-to-viewport')({
viewportWidth: 375, // 设计稿宽度(iPhone 标准 375px)
viewportHeight: 667, // 设计稿高度(可选,一般不用)
unitPrecision: 5, // 转换后保留几位小数
viewportUnit: 'vw', // 转换后的目标单位(vw / vh / vmin / vmax)
selectorBlackList: [], // 忽略的选择器(如 ['.ignore'] 不转换)
minPixelValue: 1, // 小于 1px 的值不转换(避免 0.5px 被转)
mediaQuery: false, // 是否转换媒体查询里的 px
exclude: [/node_modules/] // 排除第三方库(避免改动 UI 框架样式)
})
]
}输入输出
css
/* 输入(设计稿宽度 375px) */
.box {
width: 375px;
height: 100px;
font-size: 14px;
padding: 10px 20px;
}
/* 输出 */
.box {
width: 100vw;
height: 26.66667vw;
font-size: 3.73333vw;
padding: 2.66667vw 5.33333vw;
}忽略特定选择器
css
/* 不转换:在属性值后添加 px-to-viewport-ignore 注释 */
.box {
width: 375px; /*px-to-viewport-ignore*/
}
/* 或使用选择器黑名单 */
.ignore {
width: 375px;
}js
// postcss.config.js
module.exports = {
plugins: [
require('postcss-px-to-viewport')({
selectorBlackList: ['ignore']
})
]
}postcss-pxtorem(px 转 rem)
bash
npm install -D postcss-pxtoremjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-pxtorem')({
rootValue: 16, // 根元素字体大小
propList: ['*'], // 需要转换的属性
selectorBlackList: [], // 忽略的选择器
replace: true, // 替换原始值
mediaQuery: false, // 是否转换媒体查询
minPixelValue: 1 // 最小转换单位
})
]
}配合 rem.js 使用
js
// src/utils/rem.js
function setRem() {
const baseSize = 16
const designWidth = 375
const clientWidth = document.documentElement.clientWidth || window.innerWidth
const scale = clientWidth / designWidth
document.documentElement.style.fontSize = baseSize * scale + 'px'
}
// 初始化
setRem()
// 监听窗口变化
window.addEventListener('resize', setRem)html
<!-- index.html -->
<script src="./src/utils/rem.js"></script>输入输出
css
/* 输入 */
.box {
width: 375px;
font-size: 14px;
padding: 10px;
}
/* 输出 */
.box {
width: 23.4375rem;
font-size: 0.875rem;
padding: 0.625rem;
}postcss-px-to-viewport-8-plugin(适配 vant 4)
bash
npm install -D postcss-px-to-viewport-8-pluginjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-px-to-viewport-8-plugin')({
viewportWidth: (file) => {
return file.includes('vant') ? 375 : 750
},
viewportUnit: 'vw',
selectorBlackList: ['.ignore'],
minPixelValue: 1,
mediaQuery: false
})
]
}cssnano(CSS 压缩)
bash
npm install -D cssnanojs
// postcss.config.js
module.exports = {
plugins: [
require('cssnano')({
preset: 'default' // 或 'advanced'
})
]
}预设选项
js
// default 预设(推荐)
require('cssnano')({
preset: 'default'
})
// advanced 预设(更激进)
require('cssnano')({
preset: 'advanced'
})
// 自定义配置
require('cssnano')({
preset: ['default', {
discardComments: {
removeAll: true // 移除所有注释
},
normalizeWhitespace: true, // 压缩空白
colormin: true, // 压缩颜色
minifyFontValues: true // 压缩字体值
}]
})输入输出
css
/* 输入 */
.box {
color: #ff0000;
background-color: #ffffff;
margin: 0px 0px 0px 0px;
padding: 10px 20px;
font-size: 14px;
/* 这是注释 */
}
/* 输出 */
.box{color:red;background-color:#fff;margin:0;padding:10px 20px;font-size:14px}postcss-nested(嵌套语法)
bash
npm install -D postcss-nestedjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-nested')
]
}输入输出
css
/* 输入 */
.nav {
background: #fff;
.item {
padding: 10px;
&:hover {
background: #f5f5f5;
}
&.active {
color: #1890ff;
}
}
}
/* 输出 */
.nav { background: #fff; }
.nav .item { padding: 10px; }
.nav .item:hover { background: #f5f5f5; }
.nav .item.active { color: #1890ff; }postcss-import(@import 规则处理)
bash
npm install -D postcss-importjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-import')
]
}css
/* 可以像 Sass 一样导入文件 */
@import './variables.css';
@import './mixins.css';
@import './components/button.css';postcss-preset-env(现代 CSS 特性)
bash
npm install -D postcss-preset-envjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-preset-env')({
stage: 3, // CSS 特性阶段(0-4),3 表示候选推荐阶段,较为稳定
features: {
'nesting-rules': true, // 嵌套
'custom-properties': true, // CSS 变量
'color-mod-function': true // 颜色函数(已废弃的 CSS 提案,建议用浏览器原生 color-mix() 代替)
},
autoprefixer: {
grid: true // Grid 布局前缀
}
})
]
}支持的现代 CSS 特性
css
/* 嵌套 */
.nav {
& .item {
color: red;
&:hover {
color: blue;
}
}
}
/* 自定义属性 */
:root {
--primary: #1890ff;
}
.button {
color: var(--primary);
}
/* 自定义媒体查询 */
@custom-media --mobile (max-width: 767px);
@media (--mobile) {
.sidebar {
display: none;
}
}
/* 自定义选择器 */
@custom-selector :--heading h1, h2, h3, h4, h5, h6;
:--heading {
font-weight: bold;
}postcss-write-svg(内联 SVG)
bash
npm install -D postcss-write-svgjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-write-svg')({
utf8: true
})
]
}css
/* 生成 1px 边框 */
@svg border-1px {
height: 2px;
@rect {
fill: var(--color, #ddd);
width: 100%;
height: 50%;
}
}
.box {
border-image: url("border-1px") 2 stretch;
}postcss-aspect-ratio-mini(宽高比)
bash
npm install -D postcss-aspect-ratio-minicss
/* 输入 */
.box {
aspect-ratio: '16:9';
}
/* 输出 */
.box {
position: relative;
}
.box::before {
content: '';
display: block;
padding-top: 56.25%;
}postcss-sorting(属性排序)
bash
npm install -D postcss-sortingjs
// postcss.config.js
module.exports = {
plugins: [
require('postcss-sorting')({
order: [
'custom-properties',
'dollar-variables',
'declarations',
'at-rules',
'rules'
],
'properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'display',
'width',
'height',
'margin',
'padding',
'background',
'border',
'font',
'color'
],
'unspecified-properties-position': 'bottom'
})
]
}css
/* 输入 */
.box {
color: red;
position: absolute;
width: 100px;
top: 0;
}
/* 输出 */
.box {
position: absolute;
top: 0;
width: 100px;
color: red;
}stylelint(CSS 检查工具)
bash
npm install -D stylelint stylelint-config-standardjs
// .stylelintrc.js
module.exports = {
extends: 'stylelint-config-standard',
rules: {
'color-no-invalid-hex': true, // 禁止无效的十六进制颜色
'declaration-colon-space-after': 'always', // 冒号后必须有空格
'indentation': 2, // 缩进 2 空格
'no-missing-end-of-source-newline': true // 文件末尾必须有换行
}
}注意: 上面部分规则名(如
declaration-colon-space-after)在 stylelint v15+ 中已废弃,改用@stylistic/declaration-colon-space-after等 stylistic 插件。新版stylelint-config-standard已内置处理这些规则,建议直接用extends即可,无需手动指定。如需自定义规则,请查阅 stylelint 官方文档 确认当前版本支持的规则名。
bash
# 检查
npx stylelint "src/**/*.css"
# 自动修复
npx stylelint "src/**/*.css" --fix组合配置
移动端项目配置
js
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'postcss-nested': {},
'autoprefixer': {},
'postcss-px-to-viewport': {
viewportWidth: 375,
unitPrecision: 5,
viewportUnit: 'vw',
minPixelValue: 1
},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}PC 端项目配置
js
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'postcss-nested': {},
'autoprefixer': {},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}Vant + 自定义配置
js
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'postcss-nested': {},
'autoprefixer': {},
'postcss-px-to-viewport-8-plugin': {
viewportWidth: (file) => {
return file.includes('vant') ? 375 : 750
},
selectorBlackList: ['.ignore']
},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}Tailwind CSS 集成
bash
npm install -D tailwindcss postcss autoprefixerjs
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {}, // 解析 Tailwind 指令(@tailwind、@apply 等)
autoprefixer: {}, // 自动添加浏览器前缀
}
}js
// tailwind.config.js(Tailwind 配置文件)
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./index.html', // 扫描 HTML 文件中的类名
'./src/**/*.{vue,js,ts,jsx,tsx}' // 扫描 Vue/JS/TS 文件
],
theme: {
extend: {
colors: {
primary: '#1890ff', // 自定义主题色
}
}
},
plugins: []
}css
/* styles.css(主入口文件,必须引入这三个指令) */
@tailwind base; /* 引入基础重置样式(reset) */
@tailwind components; /* 引入组件层(可自定义组件样式) */
@tailwind utilities; /* 引入工具类(如 flex、p-4、text-center) */
/* 自定义组件层:用 @layer 把自定义样式放入 components 层 */
@layer components {
.btn {
@apply px-4 py-2 rounded font-semibold; /* @apply 复用 Tailwind 工具类 */
}
.btn-primary {
@apply bg-blue-500 text-white hover:bg-blue-600;
}
}vue
<!-- Vue 组件中使用 Tailwind -->
<template>
<div class="flex items-center justify-center h-screen bg-gray-100">
<button class="btn btn-primary">点击我</button>
</div>
</template>Vue 项目配置
Vite + Vue
js
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
'postcss-nested': {},
'autoprefixer': {},
'postcss-px-to-viewport': {
viewportWidth: 375,
selectorBlackList: ['van-']
},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}全局注入变量
js
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
css: {
postcss: './postcss.config.js',
preprocessorOptions: {
scss: {
additionalData: `@use "@/styles/variables" as *;`
}
}
}
})自定义插件
基础插件结构
js
// postcss-plugin-example.js
module.exports = (opts = {}) => {
return {
postcssPlugin: 'postcss-plugin-example',
// 遍历声明
Declaration(decl) {
if (decl.prop === 'color' && decl.value === 'red') {
decl.value = '#ff0000';
}
},
// 遍历规则
Rule(rule) {
// ...
},
// 遍历 At 规则
AtRule(atRule) {
// ...
},
// 遍历注释
Comment(comment) {
// ...
},
// 访问所有节点
Once(root) {
// 遍历所有节点
root.walkDecls((decl) => {
// ...
});
}
}
}
module.exports.postcss = true;实际示例:rem 转换插件
js
// postcss-rem-converter.js
module.exports = (opts = {}) => {
const { rootValue = 16, unitPrecision = 5 } = opts;
return {
postcssPlugin: 'postcss-rem-converter',
Declaration(decl) {
if (decl.value.includes('px')) {
const pxValue = parseFloat(decl.value);
const remValue = (pxValue / rootValue).toFixed(unitPrecision);
decl.value = `${remValue}rem`;
}
}
}
}
module.exports.postcss = true;
// postcss.config.js
module.exports = {
plugins: [
require('./postcss-rem-converter')({
rootValue: 16
})
]
}实际示例:自动添加注释
js
// postcss-add-comments.js
module.exports = () => {
return {
postcssPlugin: 'postcss-add-comments',
Rule(rule) {
// 为每个规则添加文件路径注释
if (rule.source && rule.source.input && rule.source.input.file) {
const comment = rule.first.clone({
text: `Source: ${rule.source.input.file}`,
type: 'comment'
});
rule.prepend(comment);
}
}
}
}
module.exports.postcss = true;常见问题
1. 插件顺序问题
js
// ❌ 错误顺序
plugins: [
require('cssnano'), // 压缩应该在最后
require('autoprefixer')
]
// ✅ 正确顺序
plugins: [
require('postcss-import'), // 1. 先处理导入
require('postcss-nested'), // 2. 处理嵌套
require('autoprefixer'), // 3. 添加前缀
require('cssnano') // 4. 最后压缩
]2. 开发环境不压缩
js
// postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {},
'cssnano': process.env.NODE_ENV === 'production' ? {} : false
}
}3. 排除 node_modules
js
// postcss-px-to-viewport 配置
module.exports = {
plugins: [
require('postcss-px-to-viewport')({
exclude: [/node_modules/]
})
]
}4. 配置不生效
bash
# 检查配置文件位置
# 项目根目录下
# 重启开发服务器
npm run dev
# 清除缓存
rm -rf node_modules/.cache