From d119e018075647bb3911229835689a5d7656173b Mon Sep 17 00:00:00 2001 From: rjgotten Date: Thu, 5 Mar 2015 15:55:37 +0100 Subject: [PATCH] Scoped @import (plugin) functions loading - Limited @import (plugin) support to add/addMultiple of functions - Altered @import (plugin) loading to support browser - Support proper closure scoping of @import (plugin) loaded functions --- lib/less/functions/function-caller.js | 5 ++- lib/less/functions/function-registry.js | 45 +++++++++++++++---------- lib/less/import-manager.js | 25 +++++--------- lib/less/plugins/function-importer.js | 35 +++++++++++++++++++ lib/less/tree/import.js | 7 +++- lib/less/tree/mixin-definition.js | 1 + lib/less/tree/ruleset.js | 7 ++++ test/css/import-plugin-scoped.css | 6 ++++ test/css/import-plugin-tiered.css | 6 ++++ test/css/import-plugin.css | 2 +- test/less/import-plugin-scoped.less | 9 +++++ test/less/import-plugin-tiered.less | 5 +++ test/less/import-plugin.less | 2 +- test/less/import/plugins/test.js | 7 ---- test/less/plugins/test.js | 4 +++ 15 files changed, 119 insertions(+), 47 deletions(-) create mode 100644 lib/less/plugins/function-importer.js create mode 100644 test/css/import-plugin-scoped.css create mode 100644 test/css/import-plugin-tiered.css create mode 100644 test/less/import-plugin-scoped.less create mode 100644 test/less/import-plugin-tiered.less delete mode 100644 test/less/import/plugins/test.js create mode 100644 test/less/plugins/test.js diff --git a/lib/less/functions/function-caller.js b/lib/less/functions/function-caller.js index c2b6e96c..10b13901 100644 --- a/lib/less/functions/function-caller.js +++ b/lib/less/functions/function-caller.js @@ -1,11 +1,10 @@ -var functionRegistry = require("./function-registry"); - 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); diff --git a/lib/less/functions/function-registry.js b/lib/less/functions/function-registry.js index d39230d1..bc21bcc8 100644 --- a/lib/less/functions/function-registry.js +++ b/lib/less/functions/function-registry.js @@ -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 ); \ No newline at end of file diff --git a/lib/less/import-manager.js b/lib/less/import-manager.js index 30f0eec4..79d2f3fe 100644 --- a/lib/less/import-manager.js +++ b/lib/less/import-manager.js @@ -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) { @@ -64,22 +65,8 @@ module.exports = function(environment) { return; } - if (importOptions.plugin) { - var resolvedFilename = fileManager.tryAppendExtension(path, '.js'); - if (!fileManager.isPathAbsolute(path)) { - resolvedFilename = fileManager.join(currentFileInfo.currentDirectory, resolvedFilename); - } - try { - this.context.pluginManager.importPlugin(resolvedFilename); - fileParsedFunc(null, "", resolvedFilename); - } catch(e) { - fileParsedFunc(e, "", resolvedFilename); - } - return; - } - if (tryAppendLessExtension) { - path = fileManager.tryAppendLessExtension(path); + path = fileManager.tryAppendExtension(path, importOptions.plugin ? ".js" : ".less"); } var loadFileCallback = function(loadedFile) { @@ -115,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) { diff --git a/lib/less/plugins/function-importer.js b/lib/less/plugins/function-importer.js new file mode 100644 index 00000000..b863998b --- /dev/null +++ b/lib/less/plugins/function-importer.js @@ -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 }); +}; diff --git a/lib/less/tree/import.js b/lib/less/tree/import.js index 1bacbee2..0d3c6807 100644 --- a/lib/less/tree/import.js +++ b/lib/less/tree/import.js @@ -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") { @@ -121,6 +122,10 @@ Import.prototype.eval = function (context) { } 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); diff --git a/lib/less/tree/mixin-definition.js b/lib/less/tree/mixin-definition.js index b81dd4eb..9a92ed01 100644 --- a/lib/less/tree/mixin-definition.js +++ b/lib/less/tree/mixin-definition.js @@ -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); diff --git a/lib/less/tree/ruleset.js b/lib/less/tree/ruleset.js index 9e55c5bf..18f18dda 100644 --- a/lib/less/tree/ruleset.js +++ b/lib/less/tree/ruleset.js @@ -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"); @@ -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); @@ -211,10 +216,12 @@ Ruleset.prototype.matchCondition = function (args, context) { return true; }; Ruleset.prototype.resetCache = function () { + this._funcs = null; this._rulesets = null; this._variables = null; this._lookups = {}; }; + Ruleset.prototype.variables = function () { if (!this._variables) { this._variables = !this.rules ? {} : this.rules.reduce(function (hash, r) { diff --git a/test/css/import-plugin-scoped.css b/test/css/import-plugin-scoped.css new file mode 100644 index 00000000..e42ec647 --- /dev/null +++ b/test/css/import-plugin-scoped.css @@ -0,0 +1,6 @@ +.in-scope { + result: PASS; +} +.out-of-scope { + result: test(); +} diff --git a/test/css/import-plugin-tiered.css b/test/css/import-plugin-tiered.css new file mode 100644 index 00000000..7960b9f3 --- /dev/null +++ b/test/css/import-plugin-tiered.css @@ -0,0 +1,6 @@ +.test { + result: PASS; +} +.test-again { + result: PASS; +} diff --git a/test/css/import-plugin.css b/test/css/import-plugin.css index 206a2f11..1c337756 100644 --- a/test/css/import-plugin.css +++ b/test/css/import-plugin.css @@ -1,3 +1,3 @@ .test { - result: OK; + result: PASS; } diff --git a/test/less/import-plugin-scoped.less b/test/less/import-plugin-scoped.less new file mode 100644 index 00000000..a986e8dd --- /dev/null +++ b/test/less/import-plugin-scoped.less @@ -0,0 +1,9 @@ +.in-scope { + @import (plugin) "./plugins/test"; + result : test(); +} + +.out-of-scope { + result : test(); +} + diff --git a/test/less/import-plugin-tiered.less b/test/less/import-plugin-tiered.less new file mode 100644 index 00000000..eb487738 --- /dev/null +++ b/test/less/import-plugin-tiered.less @@ -0,0 +1,5 @@ +@import "import-plugin"; + +.test-again { + result : test(); +} \ No newline at end of file diff --git a/test/less/import-plugin.less b/test/less/import-plugin.less index 7e36db15..81c2e967 100644 --- a/test/less/import-plugin.less +++ b/test/less/import-plugin.less @@ -1,4 +1,4 @@ -@import (plugin) "import/plugins/test"; +@import (plugin) "./plugins/test"; .test { result : test(); diff --git a/test/less/import/plugins/test.js b/test/less/import/plugins/test.js deleted file mode 100644 index e353168e..00000000 --- a/test/less/import/plugins/test.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - install : function( less, pluginManager ) { - less.functions.functionRegistry.add( "test", function() { - return new less.tree.Anonymous( "OK" ); - }); - } -} \ No newline at end of file diff --git a/test/less/plugins/test.js b/test/less/plugins/test.js new file mode 100644 index 00000000..f54ee82b --- /dev/null +++ b/test/less/plugins/test.js @@ -0,0 +1,4 @@ + +functions.add("test", function() { + return new tree.Anonymous( "PASS" ); +});