CSS 样式

Less 核心原理:一个 CSS 编译器 的完整工作流程

Less 的本质是一个用 JavaScript 编写的、针对 CSS 领域的编译器。它接收你写的 .less 代码,经过一系列编译步骤,最终输出浏览器能直接执行的标准 CSS 代码。

作者:洋柿子·2026-09-01·共 7 篇 · 当前 4

Less 的本质是一个用 JavaScript 编写的、针对 CSS 领域的编译器。它接收你写的 .less 代码,经过一系列编译步骤,最终输出浏览器能直接执行的标准 CSS 代码。

它所有的语法特性(变量、嵌套、混合等),本质上都是在编译阶段执行的语法糖,最终都会被转换成原生 CSS。


一、Less 完整编译流水线(5 个核心阶段)

任何一段 Less 代码,都会经过以下 5 个步骤变成 CSS:

plaintext

Less 源代码 → 词法分析 → 语法分析 → AST 转换 → 代码生成 → CSS 输出

1. 词法分析(Tokenization)

  • 作用:把 Less 代码字符串拆分成一个个有意义的 "单词"(Token)

  • 例子:

    less

    @primary: #6366f1;
    .btn { background: @primary; }
    

    会被拆分成:

    @、primary、:、#6366f1、;、.、btn、{、background、:、@、primary、;、}

  • 关键:这一步会识别出 Less 的特殊语法(如 @ 变量、& 父选择器),和普通 CSS 语法区分开

2. 语法分析(Parsing)

  • 作用:把 Token 流转换成抽象语法树(AST)

  • AST 是一个树形数据结构,完整描述了 Less 代码的结构和逻辑

  • 比如上面的代码会生成这样的 AST 结构:

    plaintext

    Root
    ├─ VariableDeclaration: @primary = #6366f1
    └─ Ruleset: .btn
       └─ Declaration: background = @primary
    

3. AST 转换(Transformation)

这是 Less 最核心的阶段,所有 Less 特有的语法特性,都是在这一步被转换成原生 CSS 的:

  • 变量替换:把所有 @primary 替换成它的值 #6366f1
  • 嵌套展开:把嵌套的选择器展开成完整的 CSS 选择器
  • 混合展开:把混合(Mixin)的代码复制到调用的地方
  • 运算执行:计算所有的数学表达式和颜色函数
  • 导入合并:把所有 @import 的文件合并成一个 AST

4. 代码生成(Code Generation)

  • 作用:遍历转换后的 AST,生成标准的 CSS 代码字符串

  • 上面的例子最终会生成:

    css

    .btn {
      background: #6366f1;
    }
    

5. 输出与优化

  • 可选步骤:压缩 CSS 代码、添加浏览器前缀、生成 Source Map
  • 最终输出一个或多个 .css 文件,供浏览器使用

二、Less 核心特性的底层实现原理

1. 变量系统:懒加载 + 作用域提升

这是 Less 最独特、也是最容易踩坑的特性,和 JavaScript 的变量提升机制几乎完全一样。

原理:

  • Less 会先扫描整个作用域的所有变量声明,再执行代码
  • 变量的最终值,是作用域内最后一次赋值的值
  • 变量有函数级作用域,内部作用域的变量会覆盖外部作用域的变量

例子:

less

// 编译前
@color: red;
.box { color: @color; } // 你以为是 red?实际是 blue!
@color: blue;

.container {
  @color: green;
  .inner { color: @color; } // 这里是 green,内部作用域覆盖外部
}

css

/* 编译后 */
.box { color: blue; }
.container .inner { color: green; }

为什么这么设计?

  • 为了和 CSS 的 "后声明覆盖先声明" 的层叠规则保持一致
  • 允许你在文件末尾覆盖前面定义的变量,这也是为什么 "在导入 Bootstrap 之前定义变量就能覆盖主题" 的原理

2. 嵌套语法:简单的字符串拼接

嵌套是 Less 最受欢迎的特性,但它的实现原理极其简单:就是把父选择器和子选择器用空格拼接起来。

原理:

  • 遇到 { 时,把当前选择器压入选择器栈
  • 遇到 } 时,把当前选择器从栈中弹出
  • 每个声明的选择器,都是栈中所有选择器的拼接结果
  • & 符号会被直接替换成当前栈中的选择器字符串

例子:

less

// 编译前
.card {
  &__header { padding: 16px; }
  &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
}

css

/* 编译后 */
.card__header { padding: 16px; }
.card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); }

3. 混合(Mixin):代码复制粘贴

混合本质上就是编译期的代码复制粘贴,没有任何运行时开销。

原理:

  • 当 Less 编译器遇到 .mixin() 调用时,会找到对应的混合定义
  • 把混合内部的所有代码,原封不动地复制到调用的位置
  • 如果混合有参数,会先进行参数替换,再复制代码

例子:

less

// 编译前
.btn-base(@bg) {
  padding: 8px 20px;
  background: @bg;
  border-radius: 8px;
}

.btn-primary {
  .btn-base(#6366f1);
  color: white;
}

css

/* 编译后 */
.btn-primary {
  padding: 8px 20px;
  background: #6366f1;
  border-radius: 8px;
  color: white;
}

关键区别:和 CSS 类的区别

  • CSS 类是运行时的:多个元素共用同一个类,浏览器只解析一次
  • Less 混合是编译期的:每次调用都会复制一份代码,会增加 CSS 文件体积
  • 所以:通用样式用 CSS 类,需要参数的样式用混合

4. 运算与函数:编译期计算

Less 中所有的数学运算和颜色函数,都是在编译阶段执行的,不会给浏览器带来任何负担。

例子:

less

// 编译前
@base: 16px;
@primary: #6366f1;

.box {
  padding: @base * 2; // 编译时计算:16px * 2 = 32px
  border-color: darken(@primary, 10%); // 编译时计算颜色
}

css

/* 编译后 */
.box {
  padding: 32px;
  border-color: #4f46e5;
}

5. 导入(@import):编译期合并

Less 的 @import 和原生 CSS 的 @import 完全不同:

  • 原生 CSS 的 @import 是运行时的:浏览器会发起多个 HTTP 请求下载 CSS 文件
  • Less 的 @import 是编译期的:编译器会把所有导入的文件合并成一个 CSS 文件

这也是为什么用 Less 可以把 CSS 拆分成多个模块,而不会影响性能的原因。


三、Less 的两种运行模式

1. 浏览器端实时编译(开发模式)

  • 原理:浏览器下载 less.js 文件,然后在客户端实时编译 Less 代码

  • 流程:

    1. 浏览器下载 HTML 页面
    2. 发现 <link rel="stylesheet/less"> 标签,下载 Less 文件
    3. 执行 less.js,在浏览器中编译 Less 代码成 CSS
    4. 把编译后的 CSS 插入到页面中
  • 优点:开发方便,不需要搭建构建环境

  • 缺点:性能差,编译过程会阻塞页面渲染,不适合生产环境

2. Node.js 预编译(生产模式)

  • 原理:在服务器端或开发机上提前把 Less 代码编译成 CSS 文件

  • 流程:

    1. 开发者写 Less 代码
    2. 运行 Less 编译器(命令行、VSCode 插件、构建工具)
    3. 生成 CSS 文件
    4. 浏览器直接下载并执行 CSS 文件
  • 优点:性能好,编译过程不影响用户体验

  • 缺点:需要搭建构建环境


四、Less 原理带来的关键启示

  1. Less 没有运行时:所有 Less 特性都在编译阶段执行,最终输出的是纯 CSS

    • 这意味着你不能在 Less 中操作 DOM、不能获取浏览器信息、不能响应用户事件
    • 这些都需要用 JavaScript 来做
  2. 变量是静态替换:Less 变量不是 CSS 变量,编译后就变成了固定的值

    • 如果你需要动态修改主题,应该使用 CSS 变量(--primary-color),而不是 Less 变量
  3. 混合会增加代码体积:频繁使用混合会导致 CSS 文件变大

    • 对于不需要参数的通用样式,优先使用 CSS 类和 :extend() 继承
  4. 作用域提升是特性不是 bug:理解了这一点,你就不会再为 "变量值不对" 而困惑

    • 这也是为什么 "在导入 Bootstrap 之前定义变量就能覆盖主题" 的根本原因

五、和 SCSS 原理的核心区别

Less 和 SCSS 虽然功能相似,但底层实现有一个关键区别:

  • Less 是解释型编译器:边解析边执行,所以支持变量作用域提升
  • SCSS 是编译型编译器:先完整解析整个文件,再从上到下执行,所以变量必须先定义后使用

这就是两者变量行为不同的根本原因。

需要我给你画一张Less 编译流程的可视化流程图,或者写一个极简版的 Less 编译器(100 行代码),让你更直观地理解它的工作原理吗?