⚗️ CSS 预处理器(Sass / Less / Stylus + PostCSS)

一句话定性

原生 CSS 长期是一门”残废”的语言:没有变量、没有嵌套、没有函数、没有模块。开发者忍无可忍,干脆在 CSS 之上发明了一整套编程语言,编译成 CSS 再交给浏览器。预处理器用一个”编译步骤”换来了 CSS 缺失的全部抽象能力——而它的最终命运,是眼看着自己发明的能力被原生 CSS 一项项收编。


一、它是什么 & 出现的时代

CSS 预处理器是一种扩展了 CSS 语法的语言,你用它的语法书写,经过编译生成标准 CSS。它诞生于 2005-2013 Ajax时代,是 CSS 工程化最早的一波尝试。

预处理器年份特点生态
Sass2006最早、最成熟,后来推出 SCSS 语法(兼容 CSS)事实标准,Ruby 起家后转 Dart-Sass
Less2009语法更接近 CSS,JS 实现,上手快因 Bootstrap 早期采用而流行
Stylus2010语法最自由(可省略括号分号)Node 生态,小众但极客喜爱

谁带火了它们

Sass 凭借强大功能成为事实标准;Less 则搭上了 Bootstrap(早期用 Less 编写)的顺风车一度大热。它们流行的时间点,恰好是 Webpack 等构建工具普及、前端开始接受”源码要先编译”这个事实的时期——预处理器需要编译,而那时编译已不再是障碍。


二、为什么会出现:原生 CSS 缺失的四样东西

原生 CSS 的"四大残废"

在 CSS 自定义属性出现之前,原生 CSS 缺失了几乎所有编程语言的基本抽象,大型样式表的维护极其痛苦:

  1. 没有变量:一个品牌主色 #1e88e5 要在几百处硬编码,换主题=全局查找替换,极易漏改。
  2. 没有嵌套:.nav .list .item a:hover 这种父子关系,只能把父选择器一遍遍重复写全。
  3. 没有 mixin / 函数:一段”垂直居中”或”清除浮动”的样式块,无法封装复用,只能复制粘贴。
  4. 没有模块化:无法把样式拆成小文件再 import 组合(原生 @import 还会产生额外 HTTP 请求,性能更差)。

预处理器精准地补齐了这四样:变量、嵌套、@mixin/@function@import/@use 部分文件(partials)。它把 CSS 从”声明清单”变成了”可编程的样式语言”。


三、核心机制 & 为什么流行

预处理器的机制本质是源码转换(source-to-source compile):你写的 .scss 经过编译器,输出浏览器能懂的 .css

它流行的真正原因,不只是功能强,而是它契合了那个时代的两个趋势:

  • 构建工具已成标配:Webpack/Gulp 让”加一个编译步骤”几乎零成本,预处理器的”必须编译”不再是负担。
  • 大型项目对可维护性的渴求:配合 CSS方法论,变量和 mixin 让 BEM 那样的体系写起来不再啰嗦,主题化、设计 token 第一次有了技术支撑。

它真正改变了什么

预处理器让前端第一次意识到:CSS 也可以”工程化”。变量、复用、模块拆分这些软件工程的常识,从此进入样式开发。即便今天它在退场,这套”样式应该被工程化”的观念已经永久留下了。


四、带来的新问题 / 副作用

强大背后的代价

  1. 强制编译步骤:简单页面也得搭一套构建链路,失去了”写个 .css 浏览器直接跑”的轻量性。
  2. 运行时是”死的”:预处理器的变量在编译时就被算成固定值。你无法在运行时根据用户操作动态改一个 Sass 变量——它早就被编译成静态字符串了。这个局限,正是原生 custom properties 后来的杀手锏。
  3. 嵌套滥用反噬:嵌套太爽,新手容易写出五六层深的嵌套,编译出 .a .b .c .d .e 这种高 specificity 选择器,反而重新点燃了优先级战争——工具放大了坏习惯。
  4. 抽象黑箱:大量 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 被默默集成进了所有主流构建工具(WebpackVite 内置)。它和 Sass 不是竞争关系——很多项目同时用 Sass 写源码、PostCSS 做后处理。更深远的是:PostCSS 的插件化模式,后来成了 UnoCSS 这类”按需生成 CSS”工具的技术地基。


六、为什么会衰落 / 现状:原生 CSS 的”收编反攻”

杀死预处理器的,是 CSS 标准自己

预处理器最核心的功能,正在被原生 CSS 一项项收编:

预处理器功能原生 CSS 收编关键差异
变量 $colorCustom 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时代