Table of Contents
Zephyr
.
Plugin functionality for modular libraries.
For an implementation using this module see air.
Install
npm i zephyr --save
Usage
var plug = require('zephyr')
, sys = plug({});
sys.plugin([require('plugin-file')]);
var component = sys();
Options
proto
: A reference to the prototype object.type
: A reference to the class to instantiate.main
: An alternative main function (factory).plugin
: Override the default plugin function.hooks
: Array of functions invoked as constructor hooks.field
: String name of field for plugin function.
Plugins
Plugins are functions invoked in the scope of a class prototype that typically decorate the prototype object (using this
) but may also add static methods or load other plugins.
Loading Plugins
To load plugin(s) call the plugin
function passing an array of plugin functions:
var plug = require('zephyr')
, sys = plug();
sys.plugin([require('plugin-file')]);
It is possible to pass a configuration object at runtime to a plugin by using an object with a plugin
function and a conf
object:
var plug = require('zephyr')
, sys = plug();
var plugins = [
{
plugin: function(conf) {
},
conf: {foo: 'bar'}
}
];
sys.plugin(plugins);
Creating Plugins
The most common use case for plugins is to decorate the class prototype with functions that are available on instances returned by the main function, these are referred to as instance plugins. Plugins may also decorate the main function these are referred to as static plugins.
Plugin implementations may mix functionality, for clarity the examples show the distinct styles.
Instance Plugins
To create an instance plugin just assign a function to this
within the plugin function:
module.exports = function plugin() {
this.chain = function() {
return this;
}
}
Now load the plugin and invoke the instance method:
var comp
, plug = require('zephyr')
, sys = plug();
sys.plugin([require('instance-plugin')]);
comp = sys();
comp.chain();
Static Plugins
To decorate the main function with static functions assign to this.main
.
module.exports = function plugin() {
this.main.method = function() {
}
}
You can then invoke the function on the plugin system:
var plug = require('zephyr')
, sys = plug();
sys.plugin([require('static-plugin')]);
sys.method();
Composite Plugins
You can depend upon other plugins by calling this.plugin
within the plugin function. This allows plugins to composite other plugins in order to resolve plugin dependencies or provide plugin groups (related plugins).
module.exports = function plugin() {
this.plugin([require('plugin-dependency')]);
}
By convention plugins are singular and plugin groups are plural.
Named Plugin
Typically a plugin will be a single module (file) and the plugin function is exported, however sometimes you may prefer to export a class or other function; in this case the plugin initialization function may be assigned to the exported object and referenced using the field
option.
Consider a module that exports a class but also wishes to expose a plugin function:
function Component(){}
Component.init = function() {
}
module.exports = Component;
We can then configure the plugin system by specifying the field
option with the name of the function, in this case init
:
var zephyr = require('zephyr')
, main = zephyr({field: 'init'});
module.exports = main;
Then we can require the file when loading the plugin and the init
function will be invoked for plugin initialization:
main.plugin([require('./component-module')]);
Configuration
Plugins accept a single argument which is a configuration object optionally passed when loading the plugin. Useful when a plugin wishes to add functionality conditionally. For example:
module.exports = function plugin(conf) {
conf = conf || {};
if(conf.ext) {
}
}
Then a consumer of the plugin system could enable the extended logic:
sys.plugin({plugin: require('conf-plugin-file'), conf: {ext: true}})
Hooks
For some plugin systems it is useful to be able to add functionality in the scope of the component instance rather than the prototype. For example to add a default listener for an event, set properties on the instance or start running logic on component creation (or based on the plugin configuration).
Pass an array as the hooks
option:
var plug = require('zephyr')
, sys = plug({hooks: []});
And an additional register
method is available on plugin
:
function hook() {
}
module.exports = function plugin() {
this.plugin.register(hook);
}
Note that hooks are only applied when the component is created with the main function:
var plug = require('zephyr')
, sys = plug({hooks: []});
sys.plugin([require('plugin-with-hook')]);
var comp = sys();
comp = new sys.Type();
Systems
A plugin system is the result of invoking the zephyr
function:
var plug = require('zephyr')
, sys = plug();
module.exports = sys;
Which allows the ability to mix multiple components using plugins in the same code base. Typically you would export the main function returned as the plugin system.
Extend
Pass the proto
and type
options to extend the plugin system:
var plug = require('zephyr');
function PluginSystem() {}
var proto = PluginSystem.prototype
var sys = plug({proto: proto, type: PluginSystem});
module.exports = sys;
For an example implementation see air.js.
Source
;(function() {
'use strict'
function plug(opts) {
opts = opts || {};
function Component(){}
var main
, hooks = opts.hooks
, proto = opts.proto || Component.prototype;
function plugin(plugins) {
var z, method, conf;
for(z in plugins) {
if(typeof plugins[z] === 'function') {
method = plugins[z];
}else{
method = plugins[z].plugin;
conf = plugins[z].conf;
}
if(opts.field && typeof method[opts.field] === 'function') {
method = method[opts.field];
}
method.call(proto, conf);
}
return main;
}
function construct() {
var args = Array.prototype.slice.call(arguments);
function Fn() {
return main.Type.apply(this, args);
}
Fn.prototype = main.Type.prototype;
return new Fn();
}
function hook() {
var comp = hook.proxy.apply(null, arguments);
for(var i = 0;i < hooks.length;i++) {
hooks[i].apply(comp, arguments);
}
return comp;
}
function register(fn) {
if(typeof fn === 'function' && !~hooks.indexOf(fn)) {
hooks.push(fn);
}
}
main = opts.main || construct;
if(Array.isArray(hooks)) {
hook.proxy = main;
main = hook;
}
main.Type = opts.type || Component;
main.plugin = proto.plugin = opts.plugin || plugin;
if(Array.isArray(hooks)) {
main.plugin.register = register;
}
proto.main = main;
return main;
}
module.exports = plug;
})();
Developer
Developer workflow is via gulp but should be executed as npm
scripts to enable shell execution where necessary.
Test
Run the headless test suite using phantomjs:
npm test
To run the tests in a browser context open test/index.html or use the server npm start
.
Start
Serve the test files from a web server with:
npm start
Cover
Run the test suite and generate code coverage:
npm run cover
Lint
Run the source tree through eslint:
npm run lint
Clean
Remove generated files:
npm run clean
Spec
Compile the test specifications:
npm run spec
Instrument
Generate instrumented code from lib
in instrument
:
npm run instrument
Readme
Generate the project readme file (requires mdp):
npm run readme
License
Everything is MIT. Read the license if you feel inclined.
Generated by mdp(1).