是,less 3.9.x–3.10.2 存在 ast 递归失控缺陷,遇无终止递归(如 @color: darken(@color, 10%))会无限展开;需升级至 less@^3.13.1 或 ^4.2.0,并确保 less-loader 版本匹配。

确认是不是Less旧版本递归失控
Less 3.9.x 到 3.10.2 存在已知 AST 递归失控缺陷,遇到 @color: darken(@color, 10%) 或无终止条件的 .loop(@n) 就会无限展开,不是你写错了,是解析器没设最大调用栈深度。
- 运行
npx lessc --version,输出lessc 3.9.0或3.10.1基本锁定问题 - 新建
test.less,只写一行@a: darken(@a, 5%);,执行npx lessc test.less—— 若卡住或报Allocation failed - JavaScript heap out of memory,就是版本问题 - 升级到
less@^3.13.1或less@^4.2.0;注意 Webpack 项目中less-loader必须匹配主版本(如less-loader@7对应less@4),错配会导致解析异常
检查递归Mixin是否切断了调用栈
Less 不支持原生循环,但旧式递归写法如 .gen(@n) when (@n > 0) { .gen((@n - 1)); } 仍是深度递归,@n > 20 时极易爆栈;新版解析器能识别尾调用优化形式,前提是结构清晰。
- ✅ 正确写法:
.gen(@n) { .loop(@i) when (@i > 0) { /* 样式 */ .loop((@i - 1)); } .loop(@n); }——.loop()被调用后立即进入守卫判断,避免闭包持续捕获变量 - ❌ 错误写法:
.gen(@n, @i: 1) when (@i = —— 每次都压入新栈帧,AST 节点数随深度指数增长 - 生成固定范围(如
.col-1到.col-12)时,直接手写展开最安全;避免在循环体里调用unit()、percentage()等加重求值负担的函数
压平嵌套选择器并拆解 @import 链
深层嵌套(如 .a { .b { .c { .d { } } })每层都新建独立 AST 节点,20 层 ≈ 百万级节点;一次性 @import "components/**/*.less" 会触发 AST 爆炸,尤其环状依赖(A→B→C→A)会导致无限解析。
- 嵌套超过 15 层必须压平,改用 BEM 命名:
.card-header-title替代.card { .header { .title { } } } - 禁用通配导入:
@import "components/*.less"路径无法静态判定,(once)失效,极易重复载入 - 用
@import (reference) "mixins.less"—— 只解析不输出 CSS;用@import (inline) "reset.css"—— 跳过 Less 解析开销 - 检查是否有
@import形成环状依赖,可临时注释掉所有@import,再逐个放开定位
区分是递归还是嵌套导致的 OOM
OOM 不是文件“太大”,而是 AST 节点爆炸式增长;单靠调大 Node 内存只是拖延崩溃,必须定位根因。
- 临时注释掉所有
.mixin() { }定义,仅保留样式规则;若仍 OOM,则大概率是嵌套层级或@import膨胀所致 - 用
node --inspect-brk node_modules/less/bin/lessc input.less output.css启动调试,在 Chrome DevTools 中录制 Heap Snapshot,观察Less.Parser或tree.Node实例是否超 10 万+ - 检查是否存在
.btn { &:hover { .btn(); } }这类 mixin 自调用 + 选择器嵌套组合,旧版解析器会反复展开直到堆溢出
真正棘手的是混合了嵌套 + 递归 + 动态变量的场景——此时 --max-old-space-size=8192 只会让它晚几秒崩,不会改变行为。重构优先级很明确:先砍掉所有未设上限的 when 递归,再压平选择器嵌套,最后清理 @import 链。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











