An easy way create apps which accept plugins.
A plugin is just a function which is executed and passed the app instance and options.
It's dead simple but powerful.
const Pluggi = require('pluggi');
const app = new Pluggi( {
myPlugin: { a: 1 }
} );
app.plugin( 'myPlugin', function(options) {
assert(this == app); // Plugin called with app instance
assert(options.a == 1); // Options keyed with the name of the plugin are passed
} );
API: .plugin( [name], [plugin], [options] )
app.plugin( 'myPlugin', function(options) {
/* ... */
} );
If function is named, plugin name is taken from the function name. This is equivalent to the above example:
app.plugin( function myPlugin(options) {
/* ... */
} );
If just name is provided, the plugin is loaded as a Node module.
app.plugin( 'my-amazing-plugin' );
assert(app.plugins.myAmazingPlugin);
This calls require('my-amazing-plugin')
internally. Note that the plugin name is converted to camel case.
The app has a property .plugins
which contains a registry of all loaded plugins.
The value for each property of .plugins
is the return value from the plugin function.
app.plugin( 'myPlugin', () => ({ aProp: 123 }) );
assert(app.plugins.myPlugin.aProp == 123);
Return values must be truthy, or an empty object {}
is used.
Let's say you're making a web framework called "monkey". You want users to be able to develop plugins for this framework.
Subclass Pluggi
and set [PLUGIN_PREFIX]
to 'monkey'
.
const {PLUGIN_PREFIX} = Pluggi;
class Monkey extends Pluggi {
constructor(options) {
super(options);
this[PLUGIN_PREFIX] = 'monkey';
}
}
Now users can develop plugins published on npm as 'monkey-routes', 'monkey-express' etc. When a plugin is loaded by name only, 'monkey-' is added to the start of the module name before it is require()
-ed.
const app = new Monkey();
app.plugin('router')
.plugin('express');
assert(app.plugins.router);
assert(app.plugins.express);
The plugins are registered as router
and express
, but the npm modules which were required were called 'monkey-router' and 'monkey-express'.
NB PLUGIN_PREFIX
is currently a string, but it may be changed to a Symbol
in a future version. This will not be considered a semver major change, so always use it via Pluggi.PLUGIN_PREFIX
.
Options are set for a plugin in 2 places - local and global. The two are merged when passed to the plugin.
const app = new Pluggi( {
router: { globalOpt: 123 }
} );
// Here is our plugin function
function router(options) {
console.log(options);
}
app.plugin( router, { localOpt: 456 } );
// Logs { globalOpt: 123, localOpt: 456 }
Global options are recorded on the app as app.options
.
Plugins will typically alter properties on the app.
To ensure two plugins do not clash, it is recommended that they respect a namespace defined by the name of the plugin. They should only make changes in two ways:
- Set property on app named with plugin name
- Return a value to be stored in the
app.plugins
object
// Example plugin
function router(options) {
this.router = ...
return { bindToExpress: function() { ... } };
}
The bindToExpress()
method can now be accessed at app.plugins.router.bindToExpress()
.
Use npm test
to run the tests. Use npm run cover
to check coverage.
See changelog.md
If you discover a bug, please raise an issue on Github. https://github.com/overlookmotel/pluggi/issues
Pull requests are very welcome. Please:
- ensure all tests pass before submitting PR
- add an entry to changelog
- add tests for new features
- document new functionality/API additions in README