我正在尝试组建一个资源构建系统,该系统将从一组目录中加载 LESS 文件:
Common
├─┬ sub
│ ├── A
│ └── B
├── C
└── ...
每个底层目录都有一个入口点 index.less。索引文件将包含 @import
语句,例如@import "colors.less";
.
我想要发生的是:
- 如果导入的文件存在于当前目录中,则使用它。
- 如果文件不存在,则使用父目录下的同名文件,递归到根目录。
因此,在解析时/Common/sub/A/index.less
,请先查找 colors.less in A
,然后 in sub
,然后再查找Common
。
我已经开发了一个两阶段构建过程的前半部分:
扫描整个目录结构并将所有文件加载到一个对象中:
common = { files: { "colors.less": "/* LESS file contents */", ... }, sub: { files: { ... }, A: { files: { "index.less": "@import 'colors.less';", ... } }, B: { files: { "index.less": "@import 'colors.less';", ... } } }, C: { files: { "index.less": "@import 'colors.less';", ... } } }
为每个底层目录构建生成的 CSS 文件。
第二阶段是我遇到一些问题的地方。首先,我创建一个解析器。
var parser = new less.Parser({
filename: 'index.less'
});
然后解析文件:
parser.parse(common.sub.A.files['index.less'], function(e, tree) {
// `tree` is the AST
});
这为我们提供了一个交付给回调的抽象语法树 (AST)。问题是 LESS 解析器@import
用自己的文件导入器解析它找到的所有语句,并将导入的文件合并到当前的 AST 中。
为了解决这个问题,我目前正在重载导入器以重写路径:
// before starting anything:
var importer = less.Parser.importer;
less.Parser.importer = function(path, currentFileInfo, callback, env) {
var newPath;
// here, use the object from phase 1 to resolve the path nearest file
// matching `path` (which is really just a filename), and set `newPath`
importer(newPath, currentFileInfo, callback, env);
};
但是,LESS 导入器仍然从磁盘读取文件。从性能的角度来看,这很糟糕,因为 (A) 我们已经在内存中拥有了文件的内容,并且 (B) 有大量的底层目录,所以我们不得不重新加载和重新解析相同的公共目录文件多次。
我想做的是在第一阶段解析每个 LESS 文件,然后在第二阶段根据需要合并 AST。
为了做到这一点,我需要防止 LESS@import
在解析期间评估节点。然后在第 2 阶段,我可以手动找到@import
AST 中的节点并合并到已经解析的 AST 中(递归地,因为它们可以包含自己的@import
s)。