less递归生成栅格类体积过大,主因是每次递归新建ast节点、选择器字符串拼接及百分比精度展开三者叠加导致指数级膨胀;应预计算基准值并显式调用避免嵌套递归。

Less 循环生成栅格类后体积过大,根本不是“循环写多了”,而是每次递归都触发完整 AST 节点创建 + 选择器字符串拼接 + 百分比精度展开,三者叠加导致编译产物指数级膨胀。
递归 mixin 每次调用都新建 AST 节点,不共享结构
Less 没有 for 循环,所谓“循环”本质是递归 mixin 调用。每轮递归都会生成独立的 AST 节点分支,哪怕只是生成 .col-1 到 .col-12,也会产生 12 套完全隔离的解析树。尤其当嵌套在 & 或媒体查询内时,节点数翻倍增长。
- 比如
.make-col(@n)每次调用都新建一个作用域,变量、选择器、声明全量复制 - 递归深度超 20 层(如生成 24 列+5 断点),AST 节点轻松破百万,直接触发 OOM
- 旧版 Less(
less@3.9.*)不会自动回收中间节点,内存持续累积
percentage() 在循环中重复展开,输出冗余小数字符串
percentage(1 / @grid-columns) 看似简洁,但每次调用都重新计算并返回带多位小数的字符串(如 "8.333333333333334%"),而这些字符串无法被 gzip 高效压缩——相同值不同小数位会被视为不同字符串。
- 12 列系统中,
.col-1到.col-12的 width 值实际只有 12 种,但编译后可能生成 12 个略有差异的百分比字面量 - 若再叠加断点(sm/md/lg),每个断点都重新算一遍,体积线性翻倍
- 正确做法是预计算单列基准:
@col-width: round(percentage(1 / @grid-columns), 4);,再统一复用
动态拼接类名(~".col-@{n}")阻止 CSS 合并与压缩
用字符串插值生成选择器,会让编译器无法识别语义关系,导致后续工具(如 cssnano)无法合并相同声明、也无法安全删除未用规则。
-
~".col-@{n}"输出的是纯文本,cssnano看不到它和.col-1是同类模式,不能做 selector dedupe - 所有
.col-*规则彼此孤立,即使声明完全一致,也不会被压缩成.col-1,.col-2{width:8.3333%} - 更糟的是:若某列类名未被 HTML 使用,Coverage 面板也标不出它“未使用”,因为它是运行时拼出来的
响应式断点叠加让体积非线性增长
每个断点单独跑一遍列循环,意味着 @media (min-width: 768px) 内的 .col-md-1 ~ .col-md-12 是全新一套选择器,而非复用已有的 .col-1 逻辑。
- 5 个断点 × 12 列 = 至少 60 组规则;若还加 offset/push/pull,轻松破百
- 错误写法:
.make-col(@n) when (@n > 0) { ... .make-col((@n - 1)); }+ 外层@each(伪语法),会触发嵌套递归,AST 更爆炸 - 安全替代:
@import (reference)引入基础栅格 mixin,各断点文件只写@media块 + 显式调用,避免跨文件递归
真正难处理的不是“怎么让循环更快”,而是“哪些列类本就不该出现在这个页面里”。Less 编译完的 CSS 里塞满 .col-xl-9,但首页 HTML 根本没用过——这种冗余,靠压缩或调大 Node 内存都解决不了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











