⚗️ CSS 预处理器(Sass / Less / Stylus + PostCSS)
一句话定性
原生 CSS 长期是一门”残废”的语言:没有变量、没有嵌套、没有函数、没有模块。开发者忍无可忍,干脆在 CSS 之上发明了一整套编程语言,编译成 CSS 再交给浏览器。预处理器用一个”编译步骤”换来了 CSS 缺失的全部抽象能力——而它的最终命运,是眼看着自己发明的能力被原生 CSS 一项项收编。
一、它是什么 & 出现的时代
CSS 预处理器是一种扩展了 CSS 语法的语言,你用它的语法书写,经过编译生成标准 CSS。它诞生于 2005-2013 Ajax时代,是 CSS 工程化最早的一波尝试。
| 预处理器 | 年份 | 特点 | 生态 |
|---|---|---|---|
| Sass | 2006 | 最早、最成熟,后来推出 SCSS 语法(兼容 CSS) | 事实标准,Ruby 起家后转 Dart-Sass |
| Less | 2009 | 语法更接近 CSS,JS 实现,上手快 | 因 Bootstrap 早期采用而流行 |
| Stylus | 2010 | 语法最自由(可省略括号分号) | Node 生态,小众但极客喜爱 |
谁带火了它们
Sass 凭借强大功能成为事实标准;Less 则搭上了 Bootstrap(早期用 Less 编写)的顺风车一度大热。它们流行的时间点,恰好是 Webpack 等构建工具普及、前端开始接受”源码要先编译”这个事实的时期——预处理器需要编译,而那时编译已不再是障碍。
二、为什么会出现:原生 CSS 缺失的四样东西
原生 CSS 的"四大残废"
在 CSS 自定义属性出现之前,原生 CSS 缺失了几乎所有编程语言的基本抽象,大型样式表的维护极其痛苦:
- 没有变量:一个品牌主色
#1e88e5要在几百处硬编码,换主题=全局查找替换,极易漏改。- 没有嵌套:
.nav .list .item a:hover这种父子关系,只能把父选择器一遍遍重复写全。- 没有 mixin / 函数:一段”垂直居中”或”清除浮动”的样式块,无法封装复用,只能复制粘贴。
- 没有模块化:无法把样式拆成小文件再
import组合(原生@import还会产生额外 HTTP 请求,性能更差)。
预处理器精准地补齐了这四样:变量、嵌套、@mixin/@function、@import/@use 部分文件(partials)。它把 CSS 从”声明清单”变成了”可编程的样式语言”。
三、核心机制 & 为什么流行
预处理器的机制本质是源码转换(source-to-source compile):你写的 .scss 经过编译器,输出浏览器能懂的 .css。
它流行的真正原因,不只是功能强,而是它契合了那个时代的两个趋势:
- 构建工具已成标配:Webpack/Gulp 让”加一个编译步骤”几乎零成本,预处理器的”必须编译”不再是负担。
- 大型项目对可维护性的渴求:配合 CSS方法论,变量和 mixin 让 BEM 那样的体系写起来不再啰嗦,主题化、设计 token 第一次有了技术支撑。
它真正改变了什么
预处理器让前端第一次意识到:CSS 也可以”工程化”。变量、复用、模块拆分这些软件工程的常识,从此进入样式开发。即便今天它在退场,这套”样式应该被工程化”的观念已经永久留下了。
四、带来的新问题 / 副作用
强大背后的代价
- 强制编译步骤:简单页面也得搭一套构建链路,失去了”写个
.css浏览器直接跑”的轻量性。- 运行时是”死的”:预处理器的变量在编译时就被算成固定值。你无法在运行时根据用户操作动态改一个 Sass 变量——它早就被编译成静态字符串了。这个局限,正是原生 custom properties 后来的杀手锏。
- 嵌套滥用反噬:嵌套太爽,新手容易写出五六层深的嵌套,编译出
.a .b .c .d .e这种高 specificity 选择器,反而重新点燃了优先级战争——工具放大了坏习惯。- 抽象黑箱:大量 mixin 和
@extend会编译出体积膨胀、难以预测的 CSS,出了问题要回去翻源码。
五、PostCSS:为什么后处理器成了”基础设施”
PostCSS 不是预处理器,而是"CSS 界的 Babel"
PostCSS 自己什么都不做——它只是把 CSS 解析成 AST,然后交给一连串插件去转换。这是和 Sass/Less”大一统语言”完全不同的哲学:插件化、按需组合。它对应的是 Babel 之于 JavaScript 的角色。
PostCSS 之所以从一个工具上升为整个前端的基础设施,靠的是一个杀手级插件:
- Autoprefixer:自动根据 Can I Use 数据,给需要的属性补浏览器前缀(
-webkit-、-moz-)。它让前端彻底不用再手写厂商前缀,这是 Flexbox/Grid 时代(布局演进史)能平稳落地的关键幕后功臣。
正因为 Autoprefixer 几乎人人都需要,PostCSS 被默默集成进了所有主流构建工具(Webpack、Vite 内置)。它和 Sass 不是竞争关系——很多项目同时用 Sass 写源码、PostCSS 做后处理。更深远的是:PostCSS 的插件化模式,后来成了 UnoCSS 这类”按需生成 CSS”工具的技术地基。
六、为什么会衰落 / 现状:原生 CSS 的”收编反攻”
杀死预处理器的,是 CSS 标准自己
预处理器最核心的功能,正在被原生 CSS 一项项收编:
| 预处理器功能 | 原生 CSS 收编 | 关键差异 |
|---|---|---|
变量 $color | Custom Properties(--color,CSS 自定义属性) | 原生变量是运行时动态的!可被 JS 修改、可响应媒体查询、可级联继承——Sass 变量做不到 |
| 嵌套 | CSS Nesting(原生 &) | 浏览器原生支持,无需编译 |
@import 模块 | @layer + 现代打包 | 标准化的级联层 |
| mixin / 函数 | 部分由 @property、未来的函数补上 | 仍是预处理器最后的护城河 |
custom properties 才是真正的代际差异
Sass 变量是”编译期常量”,编译完就消失了;CSS 自定义属性
--main-color是”运行时活变量”:能被 JavaScript 实时读写、能随:root/ 媒体查询 / 父子级联而变。这正好填补了预处理器做不到动态主题切换的死穴。换肤、暗黑模式、用户自定义主题——这些用 Sass 几乎不可能,用原生变量优雅至极。这是预处理器衰落最根本的原因:它的核心卖点(变量)被一个更强的原生能力取代了。
现状:预处理器没有死,但从”必需品”降级为”可选项”。新项目越来越多直接用”原生 CSS + custom properties + 嵌套 + PostCSS”,或者干脆转向 Tailwind。Sass 仍在大量存量项目和某些设计系统里服役,但它定义一个时代的日子已经过去了。
原生 CSS 缺变量/嵌套/mixin/模块
│
▼
Sass(2006)/ Less(2009)/ Stylus ──► 补齐抽象能力, "CSS 可工程化"觉醒
│ │
│ └──► 副作用:必须编译 + 变量是编译期死值 + 嵌套滥用
│
├──► PostCSS(后处理 + 插件化)──► Autoprefixer 成基础设施
│ └──► 插件化思想 → [[原子化CSS|Tailwind/UnoCSS]] 的地基
│
▼
原生 custom properties(运行时活变量)+ CSS Nesting + @layer
│
└──► 预处理器从"必需"降级为"可选", 让位给原生 CSS / 原子化
它教会我们的
预处理器是”社区先发明、标准后收编”这条 CSS 铁律最典型的案例。一个工具如果它的核心价值是”补语言的缺”,那么当语言自己补上了,它就必然退场。但它推动标准进化的功劳,会被永久记住——今天原生 CSS 的变量和嵌套,正是 Sass 替整个行业趟出来的路。
🔗 同组:CSS演进史 | 布局演进史 | CSS方法论 | CSS-in-JS | 原子化CSS 🔗 相关:Babel | Webpack | Vite | Grunt-Gulp-任务流时代 | 2005-2013 Ajax时代