lessc默认无缓存机制,每次全量解析AST、重算作用域;提速需手动实现文件mtime比对与tree复用,配合严格路径控制和依赖收敛。

默认用 lessc 命令反复编译,每次都是全量解析、重走 AST、重新计算变量作用域——这不是慢,是“重复劳动”。真正能提速的,是跳过已知不变的部分。
为什么 lessc 每次都从头开始?
Less 没有内置缓存机制。命令行调用本质是启动新 Node 进程,less.render() 也默认不保留上一次的 AST 或 CSS 输出。尤其在 watch 模式下,哪怕只改一个变量,所有 @import 链(包括 node_modules/ 下的文件)都会被重新加载、解析、校验。
- 变量未声明错误常在改完
variables.less后才报,说明依赖顺序没被缓存识别 -
mixins/目录下几十个文件,实际只用了 3 个,但每次编译都扫描全部 - 开启
--source-map时,map 文件生成本身不耗时,但源码映射路径解析会拖慢整个依赖遍历
用 JS API 替代命令行,手动加缓存层
构建脚本里直接调 less.render(),比反复 spawn lessc 进程省 200–400ms/次,关键在于你能控制缓存时机和粒度。
- 用
Map存{ filePath → { css, tree, mtime } },每次读文件前先比对mtime - tree 缓存后,
less.render()可传tree: cachedTree跳过解析,仅重计算 CSS - 必须显式设
paths: ['./src/less'],否则@import路径解析失败,缓存失效 - 禁用
javascriptEnabled: true——它让缓存不可靠,且无法做静态分析
示例关键逻辑:
立即学习“前端免费学习笔记(深入)”;
const cache = new Map();
async function compile(file) {
const content = await fs.readFile(file, 'utf8');
const mtime = (await fs.stat(file)).mtimeMs;
const cached = cache.get(file);
if (cached && cached.mtime === mtime) return cached.css;
const result = await less.render(content, {
paths: ['./src/less'],
math: 'always',
filename: file
});
cache.set(file, { css: result.css, tree: result.tree, mtime });
return result.css;
}
增量编译要避开两个坑
所谓“增量”,不是只编译改了的文件,而是只重算受变更影响的子树。Less 本身不支持,得靠你定义依赖关系。
- 禁止在入口文件里
@import "mixins/**/*.less"——glob 匹配会让缓存无法判断哪个 mixin 实际被引用 - 所有公共
@variable和.mixin()必须收口到单一_common.less,且每个业务文件顶部第一行@import "_common.less" - 如果用 Webpack,
less-loader的cache选项只缓存单文件结果,不跨@import;需配合resolve.alias统一路径,否则@import '../common'在不同目录下指向不同文件,缓存错乱
watch 模式卡住?其实是监听范围失控
默认 lessc --watch 会递归监听所有 @import 路径下的文件,包括 node_modules/bootstrap/less/ ——这些文件不会变,却频繁触发重编译。
- 用
--include-path=./src/less严格限定搜索范围,node_modules不在其中就完全不扫描 - 加
--disable-dotfiles防止监听.gitignore同级的临时.less备份 - 更稳的做法:别用
lessc --watch,改用chokidar监听src/less/**/*,触发时只跑compile('./src/less/main.less')
缓存和增量不是开关,是需要你定义“什么算不变”“谁依赖谁”的契约。一旦路径不收敛、@import 不受控,任何缓存策略都会降级为全量重编。


















