Skip to content

Commit d7846e2

Browse files
committed
Merge pull request #2479 from rjgotten/import-plugin
Import plugin
2 parents 54556bd + 9518763 commit d7846e2

16 files changed

+169
-28
lines changed

lib/less/environment/abstract-file-manager.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@ abstractFileManager.prototype.getPath = function (filename) {
1616
return filename.slice(0, j + 1);
1717
};
1818

19+
abstractFileManager.prototype.tryAppendExtension = function(path, ext) {
20+
return /(\.[a-z]*$)|([\?;].*)$/.test(path) ? path : path + ext;
21+
};
22+
1923
abstractFileManager.prototype.tryAppendLessExtension = function(path) {
20-
return /(\.[a-z]*$)|([\?;].*)$/.test(path) ? path : path + '.less';
24+
return this.tryAppendExtension(path, '.less');
2125
};
2226

2327
abstractFileManager.prototype.supportsSync = function() {

lib/less/functions/function-caller.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
var functionRegistry = require("./function-registry"),
2-
Expression = require("../tree/expression");
1+
var Expression = require("../tree/expression");
32

43
var functionCaller = function(name, context, index, currentFileInfo) {
54
this.name = name.toLowerCase();
6-
this.func = functionRegistry.get(this.name);
75
this.index = index;
86
this.context = context;
97
this.currentFileInfo = currentFileInfo;
8+
9+
this.func = context.frames[0].functionRegistry.get(this.name);
1010
};
1111
functionCaller.prototype.isValid = function() {
1212
return Boolean(this.func);
Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
1-
module.exports = {
2-
_data: {},
3-
add: function(name, func) {
4-
if (this._data.hasOwnProperty(name)) {
5-
//TODO warn
1+
function makeRegistry( base ) {
2+
return {
3+
_data: {},
4+
add: function(name, func) {
5+
// precautionary case conversion, as later querying of
6+
// the registry by function-caller uses lower case as well.
7+
name = name.toLowerCase();
8+
9+
if (this._data.hasOwnProperty(name)) {
10+
//TODO warn
11+
}
12+
this._data[name] = func;
13+
},
14+
addMultiple: function(functions) {
15+
Object.keys(functions).forEach(
16+
function(name) {
17+
this.add(name, functions[name]);
18+
}.bind(this));
19+
},
20+
get: function(name) {
21+
return this._data[name] || ( base && base.get( name ));
22+
},
23+
inherit : function() {
24+
return makeRegistry( this );
625
}
7-
this._data[name] = func;
8-
},
9-
addMultiple: function(functions) {
10-
Object.keys(functions).forEach(
11-
function(name) {
12-
this.add(name, functions[name]);
13-
}.bind(this));
14-
},
15-
get: function(name) {
16-
return this._data[name];
17-
}
18-
};
26+
};
27+
}
28+
29+
module.exports = makeRegistry( null );

lib/less/import-manager.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var contexts = require("./contexts"),
2-
Parser = require('./parser/parser');
2+
Parser = require('./parser/parser'),
3+
FunctionImporter = require('./plugins/function-importer');
34

45
module.exports = function(environment) {
56

@@ -65,7 +66,7 @@ module.exports = function(environment) {
6566
}
6667

6768
if (tryAppendLessExtension) {
68-
path = fileManager.tryAppendLessExtension(path);
69+
path = fileManager.tryAppendExtension(path, importOptions.plugin ? ".js" : ".less");
6970
}
7071

7172
var loadFileCallback = function(loadedFile) {
@@ -101,7 +102,11 @@ module.exports = function(environment) {
101102
newFileInfo.reference = true;
102103
}
103104

104-
if (importOptions.inline) {
105+
if (importOptions.plugin) {
106+
new FunctionImporter(newEnv, newFileInfo).eval(contents, function (e, root) {
107+
fileParsedFunc(e, root, resolvedFilename);
108+
});
109+
} else if (importOptions.inline) {
105110
fileParsedFunc(null, contents, resolvedFilename);
106111
} else {
107112
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {

lib/less/parser/parser.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,41 @@ var Parser = function Parser(context, imports, fileInfo) {
13041304
}
13051305
},
13061306

1307+
//
1308+
// A @plugin directive, used to import compiler extensions dynamically.
1309+
//
1310+
// @plugin "lib";
1311+
//
1312+
// Depending on our environment, importing is done differently:
1313+
// In the browser, it's an XHR request, in Node, it would be a
1314+
// file-system operation. The function used for importing is
1315+
// stored in `import`, which we pass to the Import constructor.
1316+
//
1317+
plugin: function () {
1318+
var path,
1319+
index = parserInput.i,
1320+
dir = parserInput.$re(/^@plugin?\s+/);
1321+
1322+
if (dir) {
1323+
var options = { plugin : true };
1324+
1325+
if ((path = this.entities.quoted() || this.entities.url())) {
1326+
1327+
if (!parserInput.$(';')) {
1328+
parserInput.i = index;
1329+
error("missing semi-colon on plugin");
1330+
}
1331+
1332+
return new(tree.Import)(path, null, options, index, fileInfo);
1333+
}
1334+
else
1335+
{
1336+
parserInput.i = index;
1337+
error("malformed plugin statement");
1338+
}
1339+
}
1340+
},
1341+
13071342
//
13081343
// A CSS Directive
13091344
//
@@ -1315,7 +1350,7 @@ var Parser = function Parser(context, imports, fileInfo) {
13151350

13161351
if (parserInput.currentChar() !== '@') { return; }
13171352

1318-
value = this['import']() || this.media();
1353+
value = this['import']() || this.plugin() || this.media();
13191354
if (value) {
13201355
return value;
13211356
}

lib/less/plugins/function-importer.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
var LessError = require('../less-error'),
2+
tree = require("../tree");
3+
4+
var FunctionImporter = module.exports = function FunctionImporter(context, fileInfo) {
5+
this.fileInfo = fileInfo;
6+
};
7+
8+
FunctionImporter.prototype.eval = function(contents, callback) {
9+
var loaded = {},
10+
loader,
11+
registry;
12+
13+
registry = {
14+
add: function(name, func) {
15+
loaded[name] = func;
16+
},
17+
addMultiple: function(functions) {
18+
Object.keys(functions).forEach(function(name) {
19+
loaded[name] = functions[name];
20+
});
21+
}
22+
};
23+
24+
try {
25+
loader = new Function("functions", "tree", "fileInfo", contents);
26+
loader(registry, tree, this.fileInfo);
27+
} catch(e) {
28+
callback(new LessError({
29+
message: "Plugin evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
30+
filename: this.fileInfo.filename
31+
}), null );
32+
}
33+
34+
callback(null, { functions: loaded });
35+
};

lib/less/tree/import.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Import.prototype.accept = function (visitor) {
5050
this.features = visitor.visit(this.features);
5151
}
5252
this.path = visitor.visit(this.path);
53-
if (!this.options.inline && this.root) {
53+
if (!this.options.plugin && !this.options.inline && this.root) {
5454
this.root = visitor.visit(this.root);
5555
}
5656
};
@@ -109,7 +109,8 @@ Import.prototype.evalPath = function (context) {
109109
return path;
110110
};
111111
Import.prototype.eval = function (context) {
112-
var ruleset, features = this.features && this.features.eval(context);
112+
var ruleset, registry,
113+
features = this.features && this.features.eval(context);
113114

114115
if (this.skip) {
115116
if (typeof this.skip === "function") {
@@ -120,7 +121,13 @@ Import.prototype.eval = function (context) {
120121
}
121122
}
122123

123-
if (this.options.inline) {
124+
if (this.options.plugin) {
125+
registry = context.frames[0] && context.frames[0].functionRegistry;
126+
if ( registry && this.root.functions ) {
127+
registry.addMultiple( this.root.functions );
128+
}
129+
return [];
130+
} else if (this.options.inline) {
124131
var contents = new Anonymous(this.root, 0, {filename: this.importedFilename}, true, true);
125132
return this.features ? new Media([contents], this.features.value) : [contents];
126133
} else if (this.css) {

lib/less/tree/mixin-definition.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
4444
i, j, val, name, isNamedFound, argIndex, argsLength = 0;
4545

4646
mixinEnv = new contexts.Eval(mixinEnv, [frame].concat(mixinEnv.frames));
47+
frame.functionRegistry = context.frames[0].functionRegistry.inherit();
4748

4849
if (args) {
4950
args = args.slice(0);

lib/less/tree/ruleset.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var Node = require("./node"),
44
Element = require("./element"),
55
Paren = require("./paren"),
66
contexts = require("../contexts"),
7+
globalFunctionRegistry = require("../functions/function-registry"),
78
defaultFunc = require("../functions/default"),
89
getDebugInfo = require("./debug-info");
910

@@ -66,6 +67,10 @@ Ruleset.prototype.eval = function (context) {
6667
rules.length = 0;
6768
}
6869

70+
// inherit a function registry from the frames stack when possible;
71+
// otherwise from the global registry
72+
ruleset.functionRegistry = ((context.frames[0] && context.frames[0].functionRegistry) || globalFunctionRegistry).inherit();
73+
6974
// push the current ruleset to the frames stack
7075
var ctxFrames = context.frames;
7176
ctxFrames.unshift(ruleset);

test/css/import-plugin-scoped.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.in-scope {
2+
result: PASS;
3+
}
4+
.out-of-scope {
5+
result: test();
6+
}

0 commit comments

Comments
 (0)