Skip to content

Import plugin #2479

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Mar 15, 2015
6 changes: 5 additions & 1 deletion lib/less/environment/abstract-file-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ abstractFileManager.prototype.getPath = function (filename) {
return filename.slice(0, j + 1);
};

abstractFileManager.prototype.tryAppendExtension = function(path, ext) {
return /(\.[a-z]*$)|([\?;].*)$/.test(path) ? path : path + ext;
};

abstractFileManager.prototype.tryAppendLessExtension = function(path) {
return /(\.[a-z]*$)|([\?;].*)$/.test(path) ? path : path + '.less';
return this.tryAppendExtension(path, '.less');
};

abstractFileManager.prototype.supportsSync = function() {
Expand Down
6 changes: 3 additions & 3 deletions lib/less/functions/function-caller.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
var functionRegistry = require("./function-registry"),
Expression = require("../tree/expression");
var Expression = require("../tree/expression");

var functionCaller = function(name, context, index, currentFileInfo) {
this.name = name.toLowerCase();
this.func = functionRegistry.get(this.name);
this.index = index;
this.context = context;
this.currentFileInfo = currentFileInfo;

this.func = context.frames[0].functionRegistry.get(this.name);
};
functionCaller.prototype.isValid = function() {
return Boolean(this.func);
Expand Down
45 changes: 28 additions & 17 deletions lib/less/functions/function-registry.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
module.exports = {
_data: {},
add: function(name, func) {
if (this._data.hasOwnProperty(name)) {
//TODO warn
function makeRegistry( base ) {
return {
_data: {},
add: function(name, func) {
// precautionary case conversion, as later querying of
// the registry by function-caller uses lower case as well.
name = name.toLowerCase();

if (this._data.hasOwnProperty(name)) {
//TODO warn
}
this._data[name] = func;
},
addMultiple: function(functions) {
Object.keys(functions).forEach(
function(name) {
this.add(name, functions[name]);
}.bind(this));
},
get: function(name) {
return this._data[name] || ( base && base.get( name ));
},
inherit : function() {
return makeRegistry( this );
}
this._data[name] = func;
},
addMultiple: function(functions) {
Object.keys(functions).forEach(
function(name) {
this.add(name, functions[name]);
}.bind(this));
},
get: function(name) {
return this._data[name];
}
};
};
}

module.exports = makeRegistry( null );
11 changes: 8 additions & 3 deletions lib/less/import-manager.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var contexts = require("./contexts"),
Parser = require('./parser/parser');
Parser = require('./parser/parser'),
FunctionImporter = require('./plugins/function-importer');

module.exports = function(environment) {

Expand Down Expand Up @@ -65,7 +66,7 @@ module.exports = function(environment) {
}

if (tryAppendLessExtension) {
path = fileManager.tryAppendLessExtension(path);
path = fileManager.tryAppendExtension(path, importOptions.plugin ? ".js" : ".less");
}

var loadFileCallback = function(loadedFile) {
Expand Down Expand Up @@ -101,7 +102,11 @@ module.exports = function(environment) {
newFileInfo.reference = true;
}

if (importOptions.inline) {
if (importOptions.plugin) {
new FunctionImporter(newEnv, newFileInfo).eval(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
} else if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
Expand Down
37 changes: 36 additions & 1 deletion lib/less/parser/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,41 @@ var Parser = function Parser(context, imports, fileInfo) {
}
},

//
// A @plugin directive, used to import compiler extensions dynamically.
//
// @plugin "lib";
//
// Depending on our environment, importing is done differently:
// In the browser, it's an XHR request, in Node, it would be a
// file-system operation. The function used for importing is
// stored in `import`, which we pass to the Import constructor.
//
plugin: function () {
var path,
index = parserInput.i,
dir = parserInput.$re(/^@plugin?\s+/);

if (dir) {
var options = { plugin : true };

if ((path = this.entities.quoted() || this.entities.url())) {

if (!parserInput.$(';')) {
parserInput.i = index;
error("missing semi-colon on plugin");
}

return new(tree.Import)(path, null, options, index, fileInfo);
}
else
{
parserInput.i = index;
error("malformed plugin statement");
}
}
},

//
// A CSS Directive
//
Expand All @@ -1301,7 +1336,7 @@ var Parser = function Parser(context, imports, fileInfo) {

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

value = this['import']() || this.media();
value = this['import']() || this.plugin() || this.media();
if (value) {
return value;
}
Expand Down
35 changes: 35 additions & 0 deletions lib/less/plugins/function-importer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
var LessError = require('../less-error'),
tree = require("../tree");

var FunctionImporter = module.exports = function FunctionImporter(context, fileInfo) {
this.fileInfo = fileInfo;
};

FunctionImporter.prototype.eval = function(contents, callback) {
var loaded = {},
loader,
registry;

registry = {
add: function(name, func) {
loaded[name] = func;
},
addMultiple: function(functions) {
Object.keys(functions).forEach(function(name) {
loaded[name] = functions[name];
});
}
};

try {
loader = new Function("functions", "tree", "fileInfo", contents);
loader(registry, tree, this.fileInfo);
} catch(e) {
callback(new LessError({
message: "Plugin evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" ,
filename: this.fileInfo.filename
}), null );
}

callback(null, { functions: loaded });
};
13 changes: 10 additions & 3 deletions lib/less/tree/import.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Import.prototype.accept = function (visitor) {
this.features = visitor.visit(this.features);
}
this.path = visitor.visit(this.path);
if (!this.options.inline && this.root) {
if (!this.options.plugin && !this.options.inline && this.root) {
this.root = visitor.visit(this.root);
}
};
Expand Down Expand Up @@ -109,7 +109,8 @@ Import.prototype.evalPath = function (context) {
return path;
};
Import.prototype.eval = function (context) {
var ruleset, features = this.features && this.features.eval(context);
var ruleset, registry,
features = this.features && this.features.eval(context);

if (this.skip) {
if (typeof this.skip === "function") {
Expand All @@ -120,7 +121,13 @@ Import.prototype.eval = function (context) {
}
}

if (this.options.inline) {
if (this.options.plugin) {
registry = context.frames[0] && context.frames[0].functionRegistry;
if ( registry && this.root.functions ) {
registry.addMultiple( this.root.functions );
}
return [];
} else if (this.options.inline) {
var contents = new Anonymous(this.root, 0, {filename: this.importedFilename}, true, true);
return this.features ? new Media([contents], this.features.value) : [contents];
} else if (this.css) {
Expand Down
1 change: 1 addition & 0 deletions lib/less/tree/mixin-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Definition.prototype.evalParams = function (context, mixinEnv, args, evaldArgume
i, j, val, name, isNamedFound, argIndex, argsLength = 0;

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

if (args) {
args = args.slice(0);
Expand Down
5 changes: 5 additions & 0 deletions lib/less/tree/ruleset.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var Node = require("./node"),
Element = require("./element"),
Paren = require("./paren"),
contexts = require("../contexts"),
globalFunctionRegistry = require("../functions/function-registry"),
defaultFunc = require("../functions/default"),
getDebugInfo = require("./debug-info");

Expand Down Expand Up @@ -66,6 +67,10 @@ Ruleset.prototype.eval = function (context) {
rules.length = 0;
}

// inherit a function registry from the frames stack when possible;
// otherwise from the global registry
ruleset.functionRegistry = ((context.frames[0] && context.frames[0].functionRegistry) || globalFunctionRegistry).inherit();

// push the current ruleset to the frames stack
var ctxFrames = context.frames;
ctxFrames.unshift(ruleset);
Expand Down
6 changes: 6 additions & 0 deletions test/css/import-plugin-scoped.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.in-scope {
result: PASS;
}
.out-of-scope {
result: test();
}
6 changes: 6 additions & 0 deletions test/css/import-plugin-tiered.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.test {
result: PASS;
}
.test-again {
result: PASS;
}
3 changes: 3 additions & 0 deletions test/css/import-plugin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.test {
result: PASS;
}
9 changes: 9 additions & 0 deletions test/less/import-plugin-scoped.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.in-scope {
@plugin "./plugins/test";
result : test();
}

.out-of-scope {
result : test();
}

5 changes: 5 additions & 0 deletions test/less/import-plugin-tiered.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "import-plugin";

.test-again {
result : test();
}
5 changes: 5 additions & 0 deletions test/less/import-plugin.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@plugin "./plugins/test";

.test {
result : test();
}
4 changes: 4 additions & 0 deletions test/less/plugins/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

functions.add("test", function() {
return new tree.Anonymous( "PASS" );
});