PostCSS实战指南从配置到插件开发的完整流程
我的写法,亲测靠谱
PostCSS 我用得不算早,但自从接手一个老项目(Webpack 4 + Vue 2)开始统一做 CSS 处理后,就再也没换过主力方案。不是因为它多炫酷,而是——它真不挑食,插件生态稳,出问题好定位。
我现在的标准配置是:PostCSS 8.x + postcss-preset-env + postcss-nested + postcss-custom-properties(手动 fallback)+ cssnano(仅生产)。不加 Autoprefixer 单独装,因为 preset-env 里已经包了,重复加反而容易版本打架。
关键在于配置文件——我从来不用 postcss.config.js 写一堆 require 和对象拼接。太容易手抖漏个逗号,或者插件顺序错位导致变量没解析就被压缩了。我现在一律用 postcss.config.cjs(注意是 .cjs 后缀),内容极简:
module.exports = {
plugins: {
'postcss-preset-env': {
stage: 3,
features: { 'custom-properties': true },
// 注意!这里必须关掉 preserve,否则 :root 里的 var() 不会被转成静态值
preserve: false
},
'postcss-nested': {},
'cssnano': {
preset: ['default', {
discardComments: { removeAll: true },
mergeLonghand: false, // 这个我关掉!不然 margin: 0 auto 会被强行合并成 margin: 0 0 0 auto,害死调试
}]
}
}
}
为什么关 mergeLonghand?去年在某个后台表格组件里被坑了整整两天:开发时样式正常,build 后所有居中 margin: 0 auto 全变成 margin: 0 0 0 auto,结果宽度计算崩了。查到 cssnano 的这个默认行为后,我直接把它钉在黑名单里了。
这几种错误写法,别再踩坑了
第一个经典反面案例:在 CSS 里写 @custom-media --sm (width <= 480px) 然后指望它自动加媒体查询前缀——醒醒,postcss-custom-media 不处理逻辑运算符,<= 是无效的。它只认 and/or/not 和括号嵌套。我试过一次,编译完啥都没变,还误以为是 PostCSS 没生效,最后发现是语法错了。
第二个坑:滥用 postcss-import。很多人图省事,在每个 SCSS 文件顶部写 @import 'vars',结果 build 时 import 路径全乱,甚至出现变量重复定义、顺序错乱。我现在的做法是:彻底禁用 postcss-import,改用 Webpack 的 css-loader 的 importLoaders + additionalData 配置全局注入变量:
// webpack.config.js 中 css-loader 配置
{
loader: 'css-loader',
options: {
importLoaders: 1,
additionalData: @import "${path.resolve(__dirname, 'src/styles/vars.css')}";
}
}
第三个最隐蔽的雷:postcss-preset-env 的 stage 值设成 4。看起来很美,支持所有 Stage 4 提案……但现实是,Stage 4 的很多特性(比如 color-mix()、relative-color-syntax)连最新版 Chrome 都没完全实现。你写了,本地跑起来是 OK 的(因为 postcss-preset-env 把它转成兜底色了),但某天浏览器升级后 behavior 反而不一致。我吃过亏——写了 color-mix(in srgb, red 50%, blue),开发环境渲染是紫的,上线后用户 Chrome 更新到 125,颜色突然变浅了。后来全换成 stage: 3,稳。
实际项目中的坑
我们有个内部组件库,打包时要导出 CSS 文件给外部项目直接 link 使用。一开始我让 PostCSS 对输出 CSS 做一遍 postcss-custom-properties + fallbacks,结果发现生成的 CSS 里全是 color: var(--primary); color: #3b82f6; 这种双声明,看着没问题,但某些旧版 iOS Safari(14.5 之前)会把 var() 解析失败后,直接忽略整条声明,导致 fallback 也不生效。折腾半天发现,得加个 preserve: false 强制替换,而不是留双份。
还有个细节:PostCSS 插件对注释的处理很敏感。比如你写:
/* @define-theme dark */
body { background: var(--bg); }
然后想用 postcss-advanced-variables 做主题切换——别试了,它根本不会识别这种注释。得老老实实写成:
:root[data-theme="dark"] {
--bg: #111;
}
不然就是白忙活。
另外,如果你用的是 Vite,注意它的 css.preprocessorOptions.postcss 配置和独立 postcss.config.cjs 是共存的,但优先级不明确。我遇到过 Vite 读了配置,但插件没生效,最后发现是 postcss.config.cjs 里用了 ES Module 语法(export default),而 Vite 在某些 Node 版本下只认 CommonJS。所以现在我强制写 module.exports = {...},不碰 export default。
最后一点实在话
PostCSS 不是银弹。它不能替代 CSS-in-JS 的动态能力,也不如 Sass 的函数丰富。但它足够轻、够灵活、出错有迹可循。我建议新人别一上来就堆 10 个插件,先跑通 preset-env + nested,再按需加。很多团队吹嘘“我们用 PostCSS 实现了主题系统”,结果代码里全是 JS 拼 CSS 字符串,那真不如直接上 emotion。
还有,别迷信“零配置”。我见过项目把所有 PostCSS 配置藏在 vue.config.js 里,几十行对象嵌套,改个兼容性都要重启整个 dev server。不如单独一个 postcss.config.cjs,清爽,好传给 CI,也方便其他同学接手。
以上是我踩坑后的总结,希望对你有帮助。这个技巧的拓展用法还有很多,后续会继续分享这类博客。有更优的实现方式欢迎评论区交流。
