Lets you make your JS code pluggable while still keeping sensitive objects and data private through closures.
npm install pluggable.js ____ __ __ __ _
/ __ \/ /_ __ ___ ___ ____ _/ /_ / /__ (_)____
/ /_/ / / / / / __ \/ __ \/ __/ / __ \/ / _ \ / / ___/
/ ____/ / /_/ / /_/ / /_/ / /_/ / /_/ / / __/ / (__ )
/_/ /_/\__,_/\__, /\__, /\__/_/_.___/_/\___(_)_/ /____/
/____//____/ /___/

pluggable.js is a tiny library that lets you make your JS project extendable via plugins,
while still keeping sensitive objects and data private through closures.
It was originally written for converse.js, to provide
a plugin architecture that allows 3rd party developers to extend and override
private objects and classes, but it does not require nor depend on either library.
Pluggable.js is only 2.5KB when minified and uncompressed.
To understand how it works under the hood, read the annotated source code.
You can try out the live demo
of the example code given below.
The files themselves are in the example folder of the
repo.
Simply run make check
*Please note: The examples to follow use the ES5 version of Javascript.
Pluggable.js however also works with ES2015 modules.*
Suppose you have a module with a private scope and two private methods,_showNotification and _fadeOut. Let's just assume for the sake of
illustration that there's a good reason why these methods are private.
What we want to do, is to make this module pluggable. In other words, we want
people to be able to write plugins for this module, in which they can:
- Access the private or closured scope of this module.
- Add new methods to the module.
- Override or extend existing methods (private or public) on this module.
To make the module pluggable, we simply call pluggable.enable(module);.
Once we've made this call, the module object will have a new propertypluginSocket, which you can think of as the thing into which the plugins are
plugged into. It is an instance of PluginSocket, which represents the plugin
architecture and manages all the plugins.
Additionally, we need to expose some way for plugins to register themselves.
Since the module itself is private, we'll need to expose the registerPlugin
method on the pluginSocket via a public method.
And then finally, once all plugins have been registered, they need to be
initialized by calling initializePlugins on the PluginSocket instance.
For example:
`` Javascript`
module.pluginSocket.initializePlugins();
Let's now look at the code for our module (which will go into a file called app.js):
` Javascript
// An example application, used to demonstrate how pluggable.js can
// allow a module to be made pluggable.
(function () {
var module = this;
// A private method, not available outside the scope of this module.
module._showNotification = function (text) {
/ Displays a notification message /
var el = document.createElement('p');
el.setAttribute('class', 'notification');
el.appendChild(document.createTextNode(text));
el = document.getElementById('notifications').appendChild(el);
setTimeout(_.partial(module._fadeOut, el), 3000);
return el;
};
// Another private method, not available outside the scope of this module.
module._fadeOut = function (el) {
/ Fades out the passed in DOM element. /
el.style.opacity = 1;
(function fade() {
if ((el.style.opacity -= 0.1) < 0) {
el.remove();
} else {
setTimeout(fade, 25);
}
})();
};
// Initialize this module.
// -----------------------
// There are two buttons for which we register event handlers.
//
// This will be a public method.
module.initialize = function () {
var notify_el = document.getElementById('notify');
notify_el.addEventListener('click', function () {
module._showNotification('This is a notification.');
});
var enable_el = document.getElementById('enable');
enable_el.addEventListener('click', function () {
this.setAttribute('disabled', 'disabled');
module.pluginSocket.initializePlugins();
});
};
// Register a plugin for this module.
// ----------------------------------
// This is a wrapper method which defers to the registerPlugin methodpluginSocket
// of pluggable.js, which is on the property of themodule
// private object that was made pluggable, viapluggable.enable(module).
//
//
// This will be a public method.
module.registerPlugin = function (name, plugin) {
module.pluginSocket.registerPlugin(name, plugin);
};
// Calling pluggable.enable on the closured module object, will make it
// pluggable. Additionally, it will get the pluginSocket attribute, which
// refers to the object that the plugins get plugged into.
pluggable.enable(module);
// Declare the two public methods
var _public = {
'initialize': module.initialize,
'registerPlugin': module.registerPlugin
};
window.myApp = _public;
return _public;
})();
``
So, as you can see in the example above, the module has a private method
_showNotification, which will show a notification message in the page.
Let's now create a plugin which overrides this method, so that we can modify the
way the notification message will be displayed.
By the way, multiple plugins can override the same method. As long as
each overriding method calls the original method, via the __super__ property,
the method call will travel up through all the overrides (in the reverse order
in which the plugins were registered) back to the original method.
The way to call the method being overridden, is like this:
` Javascript`
this.__super__.methodName.apply(this, arguments);
where methodName is the name of the method. In our example, this will beshowNotification.
So, with that said, here's what the plugin code (which goes into the file plugin.js)
will look like:
` JavascriptregisterPlugin
(function () {
// We call the public method from our module in app.js
window.myApp.registerPlugin('my-plugin', {
overrides: {
// Here you specify your overrides of methods or objects on the
// module that has been made pluggable.
// Override _showNotification to change the color of notification
// messages.
_showNotification: function (text) {
// When overriding a method, you can still call the original method
// via this.__super__. To properly call with the proper this.apply(this, arguments)
// context and parameters, use .
var el = this.__super__._showNotification.apply(this, arguments);
// Now we simply set another class on the element containing
// the notifications, so that they'll appear in a different
// color.
el.setAttribute('class', el.getAttribute('class')+' extra');
}
// BTW, new methods which don't exist yet can also be added here.
},
initialize: function (socket) {
// The initialize function gets called as soon as the plugin is
// loaded by pluggable.js's plugin machinery.
// We are passed in an instance of the PluginSocket, whichpluggable.enable
// represents the plugin architecture and gets created when
// is called on an object.
// The PluginSocket instance is also accessible via the pluginSocketpluggable.enable
// attribute, on the module that was passed to .
// You can get hold of the module which was made pluggable
// via the plugged property of the PluginSocket instance.
// Once you have the module, you can access its private properties
// and call its private methods.
// Here, once this plugin is initialized, we show a notification.
socket.plugged._showNotification(
"The plugin has been enabled. Notifications are now a different color."
);
}
});
})();
`$3
The plugin has three important parts to it. Firstly, a call to register the
plugin must be made, and then passed in with this call is an object which
represents the plugin itself.
This plugin object has an overrides key, which is another object containing methods
and objects that will override similarly named methods and object properties on the
module itself.
Additionally, the initializePlugins method has been called on thepluginSocket instance.
This happens inside our module in app.js and this methods should generally be
called once, after all plugins have been registered and after the module being
made pluggable has itself been initialized.
In the initialize method, you have access the module's scope, which would
otherwise not be available to you.
So, as you can see, the plugin lets us achieve our three goals stated earlier:
We have access to the module and all its properties and methods inside the
initialize method of our plugin.
We can add new methods to our module, either inside the initialize method ofoverrides
our plugin, or by stating them declaratively in object.
We can override existing methods and object properties via the overrides`
object.
For issues and requests, please use the project's issue tracker.
You can reach me via my website's contact form and you can also follow me on twitter.