I'm trying to put together a resource build system which will load LESS files from a set of directories:
Common
├─┬ sub
│ ├── A
│ └── B
├── C
└── ...
Each bottom-level directory will have an entry point, index.less. The index file will include @import statements, for example @import "colors.less";.
What I would like to happen is:
So when parsing /Common/sub/A/index.less, look for colors.less in A, then in sub, then Common.
I've already developed the first half of a two-stage build process:
Scan the entire directory structure and load all files into an object:
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';", ... } }
}
Build resulting CSS file for each bottom-level directory.
Phase two is where I've run in to some issues. First, I create a parser.
var parser = new less.Parser({
filename: 'index.less'
});
Then parse the file:
parser.parse(common.sub.A.files['index.less'], function(e, tree) {
// `tree` is the AST
});
This gets us an abstract syntax tree (AST) delivered to the callback. The problem is that the LESS parser resolves all @import statements it finds with its own file importer, and merges the imported file into the current AST.
To get around this, I am currently overloading the importer to rewrite the path:
// 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);
};
However, the LESS importer still reads the file from disk. This is bad from a performance perspective since (A) we already have the file's contents in memory and (B) there are are large number of bottom-level directories, so we're forced to re-load and re-parse the same common files multiple times.
What I'd like to do is parse every LESS file in phase one, and then merge the ASTs as necessary during phase two.
In order to do this, I need to prevent LESS from evaluating @import nodes during parsing. Then in phase 2, I can manually find the @import nodes in the AST and merge in the already-parsed ASTs (recursively, since they can include their own @imports).
An interesting problem. Without delving too deep into the implementation of the Less parser (which I assume you want to avoid as much as I do at this moment) why not simply add a pre-parsing step: Read all the .less files and comment out the imports?
So, right before running a less file through the parser, you can read it yourself and write to the file, commenting out the @import lines, and taking note of them as you do. Then run it through the parser and you get an AST to which you can attach the import information you grabbed earlier.
Now you can knit together all the AST's in whatever fashion you were already planning to.
Make sure to keep the less files in the state you found them in. Either un-comment those lines, or a slightly better method would be to copy each file you want to work on, comment out the imports, parse it, then delete. This way you wouldn't have to worry about polluting the original files.
Seems like a quick way of circumventing the issue. Unless you would rather do something like tell the less Parser itself to ignore @import tokens. (Just taking a random stab here, but perhaps if you edit the "import" function in lib/less/parser.js on line 1252 to return new(tree.Comment)(dir); it might just parse every @import token as a comment.