UNPKG

in

Version:

Plugin loader. Plays with Ware

387 lines (290 loc) 8.47 kB
# in Generic plugin loader facility. <!-- toc orderedList:0 --> - [in](#in) - [Getting Started](#getting-started) - [Documentation](#documentation) - [constructor options](#constructor-options) - [context](#context) - [mountDirectory(directory,options,context)](#mountdirectorydirectoryoptionscontext) - [find(directory)](#finddirectory) - [sort(plugins)](#sortplugins) - [sortFilter](#sortfilter) - [sortByDependencies](#sortbydependencies) - [normalize(plugins)](#normalizeplugins) - [String](#string) - [Array](#array) - [Object](#object) - [Mixed](#mixed) - [filter(plugins)](#filterplugins) - [load(plugins, options={})](#loadplugins-options) - [mount](#mount) - [mountHandler](#mounthandler) - [afterMount(context)](#aftermountcontext) - [Examples](#examples) - [Release History](#release-history) - [License](#license) <!-- tocstop --> ## Getting Started Install the module with: `npm install in` ```js const PluginLoader = require('in'); ``` A plugin should expose a `init` function. ```js module.exports.init = function(app, config){}; ``` A module can expose a `priority` value: ```js module.exports.priority = 5000; ``` A negative value indicates a higher priority A module can expose a 'dependencies' value: ```js module.exports.dependencies = ['logger']; ``` ## Documentation A plugin is nothing more than a regular Node module. By default, we expect plugins to expose an `init` function that takes two arguments: * [context](#context) * config ### constructor options * [context](#context) * [basepath](#basepath) * [normalize](#normalizeplugins) * [mountHandler](#mounthandler) * [sortFilter](#sortfilter) * [afterMount](#aftermountcontext) ### context This is where all plugins will be mounted. This would normally be your application instance. ### mountDirectory(directory,options,context) It will mount all plugins found in directory into the provided context. This is in effect applying [find](#finddirectory), [filter](#filterplugins), [load](#loadplugins-options), [sort](#sortplugins), and [mount](#mount) in that order. ### find(directory) Scans a directory for files and directories, returning a list of absolute paths to the files. It `target` is not an absolute path, we resolve it against `basepath`. ### sort(plugins) Sorts an array of plugins after they have been loaded. By default it uses `sortFilter`: #### sortFilter It will looks for `module.exports.priority` and sort based on that: ```js function _sortFilter(plugins){ function filter(a, b) { function p(i){ return i.priority === undefined ? 0 : i.priority; } return p(a) < p(b) ? -1 : 1; } return plugins.sort(filter); } ``` ##### sortByDependencies The library also exposes a [sortByDependencies][sortbydependencies] filter, which you can use instead of the default `sortFilter`. ```js const loader = new PluginLoader({ sortFilter: require('in').sortByDependencies }); ``` Modules should expose a `dependencies` array listing the ids of depended modules. ```js module.exports.dependencies = ['logger', 'persistence']; ``` ### normalize(plugins) When we call load we apply the `normalize` function which will ensures that `plugins` can be any of the following: #### String ```js const plugins = '/Users/application/plugins/authentication'; ``` Output after calling `normalize`: ```js [ { "id": "authentication", "path": "/Users/application/plugins/authentication", "config": {} } ] ``` #### Array ```js const plugins = ['/Users/application/plugins/authentication']; ``` Output after calling `normalize`: ```js [ { "id": "authentication", "path": "/Users/application/plugins/authentication", "config": {} } ] ``` #### Object ```js const plugins = { '/Users/application/plugins/authentication': { hash: 'sh1' } }; ``` Output after calling `normalize`: ```js [ { "id": "authentication", "path": "/Users/application/plugins/authentication", "config": { "hash": "sh1" } } ] ``` #### Mixed ```js const plugins = [ {'/Users/application/plugins/authentication':{ hash: 'sh1' }}, 'debug' ]; ``` Output after calling `normalize`: ```js [ { "id": "authentication", "path": "/Users/application/plugins/authentication", "config": { "hash": "sh1" } }, { "id": "debug", "path": "debug", "config": {} } ] ``` ### filter(plugins) Public: Apply `minimatch` patterns against `paths`, an array of paths. The default pattern is `['**', '!node_modules', '!.git']` Returns a `Promise` which once resolved will contain an Array of filtered paths. ### load(plugins, options={}) Given a list of plugins, create a plugin object with metadata and the result of `require`ing the module. We create a bean per plugin: ```js { id: 'logger', path: '/Users/in/examples/plugins/logger.js', config: {}, plugin: { init: [Function], priority: -100 }, isLocal: true } ``` ### mount Makes plugins available to the provided context by calling `mountHandler` to previously loaded plugins. ### mountHandler Adds a `plugin` to the provided `context`. ```js function _mount(bean, context, config={}){ config = extend({}, bean.config, config); var plugin = bean.plugin; if(typeof bean.config === 'function') return bean.config(plugin, context, config); if(typeof bean.config.mount === 'function') return bean.config.mount(plugin, context, config); if(typeof plugin.init === 'function') return plugin.init(context, config); return context[bean.id] = plugin; } ``` ### afterMount(context) Function that will be called right after `mount` ## Examples Look at the [examples][examples] directory. Run it with `node examples/index.js`. Directory structure: - index.js - plugins - repl.js - pubsub.js - logger.js - authentication - index.js ```js const PluginManager = require('..'); const EventEmitter = require('events'); const app = new EventEmitter(); app.on('plugins.ready', _ => { app.logger.info('Application plugins loaded'); }); const manager = new PluginManager({ context: app, basepath: __dirname, afterMount: (context) => context.emit('plugins.ready') }); manager.mountDirectory('./plugins') .catch(console.error); ``` `logger.js`: ```js module.exports.init = function(app, config) { app.logger = console; app.logger.info('Plugin logger loaded!'); }; module.exports.priority = -100; ``` `repl.js`: ```js module.exports.init = function(app, config) { app.repl = { context: {app} }; app.logger.info('Plugin REPL loaded!'); }; ``` `pubsub.js`: ```js module.exports.init = function(app, config) { app.pubsub = { publibsh: function(type, event){ app.logger.warn('publish: %s. payload: %s', type, event); }, subscribe: function(type, handler){ app.logger.warn('subscribe to %s', type); } }; app.logger.info('Plugin pubsub loaded!'); }; ``` `authentication/index.js`: ```js module.exports.init = function(app, config) { app.auth = { check: function(){} }; app.logger.info('Plugin auth loaded!'); }; module.exports.priority = 500; ``` `node examples/load/index.js`: ``` Plugin logger loaded! Plugin REPL loaded! Plugin pubsub loaded! Plugin auth loaded! Application plugins loaded ``` ## Release History * 2020-06-04: v0.18.2 hotfix release * 2020-06-04: v0.18.0 Add support for sub directories in plugins target dir * 2019-09-08: v0.17.0 Update version of dependencies * Export `normalizePath` from `lib/normalizeArguments.js` * Export `getIdFromPath` from `lib/normalizeArguments.js` * 2019-08-26: v0.16.2 Update version of dependencies * 2019-07-13: v0.16.0 Remove synchronous file access * 2019-07-13: v0.15.0 Update version of dependencies * 2016-11-24: v0.9.0 Added `sortByDependencies` * 2016-11-18: v0.8.0 Update mount to take in `afterMount` in options * 2016-11-14: v0.7.0 `normalizePath` use `basepath` from argument * 2016-11-13: v0.6.0 Refactoring mount * 2016-10-24: v0.5.0 Added `afterMount` * 2016-10-23: v0.4.0 Big update * Added examples * Added documentation * Added `mountDirectory` ## License Copyright (c) 2015 goliatone Licensed under the MIT license. [examples]: ./examples [sortbydependencies]: ./lib/sortbydependencies.js