MinimaJs is a OSGi-like, simple yet powerful plugin framework, based on NodeJS, developed by ES6, with IDE VSCode.
let minima = new Minima(path.join(__dirname, 'plugins'));
minima.start();
The architecture of minimajs is shown as below.
There are three features:
$ npm install --g babel-cli
Install with npm:
$ npm install --save minimajs
The Minima is a plugin framework container. We need to create a plugin framework instance and start it.
import { Minima } from 'minimajs';
import path from 'path';
let minima = new Minima(path.join(__dirname, 'plugins'));
minima.start();
Plugin Examples
Create a simple plugin in plugins directory as below.
The plugin.json in demoPlugin folder is shown as below. It define a logService here.
{
"id": "demoPlugin",
"startLevel": 5,
"version": "1.0.0",
"services": [{
"name": "logService",
"service": "LogService.js"
}]
}
The Activator.js in demoPlugin folder is shown as below. It handles the 'commands' extensionPoint here.
import { Minima, Extension, ExtensionAction, PluginContext, log } from 'minimajs';
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.handleCommandExtensions = this.handleCommandExtensions.bind(this);
this.extensionChangedListener = this.extensionChangedListener.bind(this);
}
/**
* 插件入口
*
* @param {PluginContext} context 插件上下文
* @memberof Activator
*/
start(context) {
context.addExtensionChangedListener(this.extensionChangedListener);
this.handleCommandExtensions();
}
handleCommandExtensions() {
let extensions = Minima.instance.getExtensions('commands');
for (let extension of extensions) {
let Command = extension.owner.loadClass(extension.data.command).default;
let command = new Command();
command.run();
}
log.logger.info(`The commands extension size is ${extensions.size}.`);
}
extensionChangedListener(extension, action) {
this.handleCommandExtensions();
}
stop(context) {}
}
Then create another plugin named demoPlugin2 as below. The demoPlugin2 will consume the logService registered by demoPlugin and register the extension to 'commands' extensionPoint.
In the EchoCommand, the demoPlugin2 will load a class from demoPlugin.
// 1 plugin.config
{
"id": "demoPlugin2",
"version": "1.0.0",
"dependencies": [{
"id": "demoPlugin",
"version": "1.0.0"
}],
"extensions": [{
"id": "commands",
"data": {
"name": "echo",
"command": "commands/EchoCommand.js"
}
}]
}
// 2 Activator.js
import { PluginContext, log } from 'minimajs';
export default class Activator {
static logService = null;
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
/**
* 插件入口
*
* @param {PluginContext} context 插件上下文
* @memberof Activator
*/
start(context) {
let logService = context.getDefaultService('logService');
if (!logService) {
throw new Error('The logService can not be null.');
}
Activator.logService = logService;
logService.log('Get the logService successfully.');
}
stop(context) {}
}
After starting the framework, we can see the logs as below.
[2017-7-30 12:03:50.833] [INFO] log - Loading plugins from /Users/lorry/VSCodeProjects/minima-github/minimajs/example/build/plugins.
[2017-7-30 12:03:50.839] [INFO] log - Plugin demoPlugin is loaded from /Users/lorry/VSCodeProjects/minima-github/minimajs/example/build/plugins/demoPlugin.
[2017-7-30 12:03:50.840] [INFO] log - Plugin demoPlugin2 is loaded from /Users/lorry/VSCodeProjects/minima-github/minimajs/example/build/plugins/demoPlugin2.
[2017-7-30 12:03:50.840] [INFO] log - Plugins are loaded from /Users/lorry/VSCodeProjects/minima-github/minimajs/example/build/plugins completed.
[2017-7-30 12:03:50.841] [INFO] log - There are 2 plugins loaded.
[2017-7-30 12:03:50.845] [INFO] log - Starting the plugins with active initializedState.
[2017-7-30 12:03:50.846] [INFO] log - The plugin demoPlugin is starting.
[2017-7-30 12:03:50.929] [INFO] log - The commands extension size is 0.
[2017-7-30 12:03:50.954] [INFO] log - The plugin demoPlugin is active.
[2017-7-30 12:03:50.955] [INFO] log - The plugin demoPlugin2 is starting.
[2017-7-30 12:03:50.979] [INFO] console - Get the logService successfully.
[2017-7-30 12:03:51.033] [INFO] console - The echo command is executed.
[2017-7-30 12:03:51.034] [INFO] log - The commands extension size is 1.
[2017-7-30 12:03:51.034] [INFO] log - The plugin demoPlugin2 is active.
[2017-7-30 12:03:51.034] [INFO] log - The plugins with active initializedState are started.
Typical usage as below.
let minima = new Minima(path.join(__dirname, 'plugins'));
minima.start();
The Minima instance will find all plugins below the 'plugins' directory and load them to the framework. Then the minimajs framework will resolve the dependencies between the plugins. After calling minima.start, the minimajs framework will start the resolved plugins followed by the startLevel of plugin. The smallest startLevel, the first to be started.
You can use Minima to register global service, thus all plugins can use this service when starting.
let minima = new Minima(path.join(__dirname, 'plugins'));
let logService = new LogService();
minima.addService('logService', logService);
minima.start();
The Activator.js of plugin can use this global service directly.
export default class Activator {
start(context) {
let logService = context.getDefaultService('logService');
logService.log('Get the logService successfully.');
}
stop(context) {}
}
You can use the Minima.instance to access the framework in the each plugin. The Minima framework provides the features as below:
export default class Activator {
start(context) {
let logService = Minima.instance.getDefaultService('logService');
// Or use the context instead
// let logService = context.getDefaultService('logService');
logService.log('Get the logService successfully.');
}
stop(context) {}
}
In minimajs, the plugin = plugin.json + Activator.js(Optional) + Other JS files or resource files(Other resource, Optional). The plugin directory is a directory which contains the plugin.json file. The plugin.json is to describe the details about the plugin as shown below.
Additionally, a plugin will define a Activator commonly. The Activator is a JS file with start(context) and stop(context) functions defined. The start(context) is called when plugin is starting, while the stop(context) is called when stopping. The default Activator file is Activator.js. This file is optional, thus, the plugin is started or stopped directly. The plugin may include other files also, such as HTML, CSS, and so on.
Below is a fully plugin.json example.
{
"id": "demoPlugin",
"name": "demoPlugin",
"description": "The demo plugin.",
"version": "1.0.1",
"startLevel": 5,
"initializedState": "active",
"activator": "PluginActivator.js",
"stoppable": true,
"dependencies": [{
"id": "demoPlugin",
"version": "1.0.0"
}],
"services": [{
"name": "myService",
"service": "MyService.js",
"properties": {
"vendor": "lorry"
}
}],
"extensions": [{
"id": "myExtension",
"data": {
"extensionData": "lorry"
}
}, {
"id": "myExtension2",
"data": {
"extensionData": "lorry2"
}
}, {
"id": "minima.menus",
"data": [{
"url": "view.js",
"text": "view"
}]
}]
}
The basic description of plugin.json is shown as below.
The dependencies attribute is to describe the dependent plugins of current plugin. It is Optional. when the dependencies is not defined, it means there is not any dependent plugins. The dependencies attribute is an Array and each dependency contains the id and version attribute. The version attribute is Optional, and use "1.0.0" by default. The id is the dependent plugin id, the version is the minimize version of the dependent plugin. Below is the typical usage.
[{
"id": "demoPlugin",
"version": "1.0.0"
}]
The services attribute of plugin.json is described as below.
The services is a array definition. Each service element contains name, service and properties attributes. The name is the unique service name, used to find the service instance. The service attribute defines the service JS file path relative to the plugin directory. The properties attribute is used to filter the target service. Below is the typical usage of services attribute.
[{
"name": "myService",
"service": "service/MyService.js",
"properties": {
"vendor": "lorry"
}
}]
The extensions attribute of plugin.json is to defined the extensions defined by current plugin, and it is Optional. The extension feature provides a ExtensionPoint-Extension extensibility model of plugin framework. The plugin which can be extended by others will defined a named ExtensionPoint, and it will receive the extension data registered by other plugin. The plugin which extends the functionalities of another will define the extensions attribute in the plugin.json. Each extension definition contains id and data attributes. The id is the unique ExtensionPoint ID, the data is the extension content which will register to the ExtensionPoint and can be got by getExtensions method of Minima.Instance or PluginContext.getExtensions.
[{
"id": "myExtension",
"data": {
"extensionData": "lorry"
}
}, {
"id": "myExtension2",
"data": {
"extensionData": "lorry2"
}
}, {
"id": "minima.menus",
"data": [{
"url": "view.js",
"text": "view"
}]
}]
The data attribute is any value, which is determined by the plugin which exposes the ExtensionPoint. The data attribute can be string, array, object, and so on.
The Activator is to define the entry and the exit. Each Activator contains two functions named start and stop. When the plugin is starting, the start function will be called. And the stop function is called when the plugin is stopped.
The Activator is Optional. The plugin can be defined without a Activator. Thus it will start or stop directly. Additionally, the default Activator file is Activator.js in the plugin directory. If you will define a Activator with another file name, you need to specify the activator attribute of plugin.json.
The Activator is defined with below style.
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
start(context) {
// TODO: do something when starting
}
stop(context) {
// TODO: do something when stopping
}
}
There is a parameter named context of start and stop function in each Activator. The context is a PluginContext instance. You can use the PluginContext to access the framework functionalities, such as, to add or get service, get extensions, get plugins, install another plugin, and so on.
The Activator is used to initialize the resources of current plugin, and release them after stopping. Any exception occurs in the start function will block the the starting of a plugin, the plugin start will keep in 'resolved' state. But the plugin will still be stopped if an exception occurs in the stop method.
The PluginContext is a common class when defining a plugin. It provides the below functionalities.
The Plugin is a another common class when defining a plugin. It provides the below functionalities.
The minimajs framework supports to install, start, stop and uninstall in the runtime. Each plugin has installed, resolved, starting, active, stopping, uninstalled state definitions.
When the framework install a plugin, it will read the plugin.json file, validate the plugin.json, and create the Plugin instance. If the Plugin is installed, its state is 'installed'.
After installing a plugin, the framework will resolve its dependencies immediately. It means, the plugin will find all dependent plugins. If the dependent plugin does not exist or can not be resolved, the plugin can not be resolved successfully, thus its state still be 'installed', otherwise, its state is 'resolved'.Once the plugin is in the 'resolved' state, it means the plugin is ready to be started.
When the minimajs framework starting a plugin, it will follow below activities sequence.
When the minimajs framework stopping a plugin, it will follow below activities sequence.
When the minimajs framework uninstall a plugin, it will follow below activities sequence.
The service in the minimajs framework is used to implement the interactive between the plugins. One plugin register a plugin, thus another plugin can consume the service. The service can be register, unregister in the runtime.
The service provides some common functionalities, such as defining a LogService class as below.
export default class LogService {
log(message) {
if (message) {
console.log(message);
}
}
}
We can register a service in the plugin.json or PluginContext instance of start function in the Activator.
You can specify the name, service JS file path relative to the plugin directory, and service properties. Note that the service properties is used to filter the services register by the same service name.
{
"id": "demoPlugin",
"startLevel": 5,
"version": "1.0.0",
"services": [{
"name": "logService",
"service": "service/LogService.js",
"properties": {
"vendor": "lorry"
}
}]
}
Also we can register the service in the Activator.
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
start(context) {
this.logServiceRegistry = context.addService('logService', new LogService());
}
stop(context) {
context.removeService(this.logServiceRegistry); // This is Optional, it will be done by the minimajs framework when stopping.
}
}
Remove the service in the stop function is Optional, the minimajs framework will remove the services registered by the plugin when stopping it.
The plugin can get the service use the PluginContext or Minima.instance.
Below is the usage of PluginContext. You may get the empty service if service is not registered or is unregistered, and need to make sure the service is not null before using the service.
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
}
start(context) {
let logService = context.getDefaultService('logService');
// or let logService = context.getDefaultService('logService', {vendor : 'lorry'});
let logServices = context.getServices('logService');
// or let logServices = context.getServices('logService', {vendor : 'lorry'});
}
stop(context) {
}
}
You can use the PluginContext or Minima.instance to listen the service changed event. Such as below.
export default class Activator {
static logService;
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.serviceChangedListener = this.serviceChangedListener.bind(this);
}
start(context) {
Activator.logService = context.getDefaultService('logService');
context.addServiceChangedListener
}
serviceChangedListener(name, action) {
if (name === 'logService') {
Activator.logService = context.getDefaultService('logService');
}
}
stop(context) {
}
}
The extension feature provides the functionality that a plugin can extend the functionalities of another plugin without change any codes. This feature follows the ExtensionPoint-Extension extensibility model. The extension is available when the plugin is started and is removed after stopped.
The extension which will be extended in the runtime, need to define a extensionPoint id. The extensionPoint is unique. And the plugin need to handle the extensions registered by other plugins.
import { Minima, Extension, ExtensionAction, PluginContext, log } from 'minimajs';
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.handleCommandExtensions = this.handleCommandExtensions.bind(this);
this.extensionChangedListener = this.extensionChangedListener.bind(this);
}
/**
* 插件入口
*
* @param {PluginContext} context 插件上下文
* @memberof Activator
*/
start(context) {
context.addExtensionChangedListener(this.extensionChangedListener);
this.handleCommandExtensions();
}
handleCommandExtensions() {
let extensions = Minima.instance.getExtensions('commands');
for (let extension of extensions) {
let Command = extension.owner.loadClass(extension.data.command).default;
let command = new Command();
command.run();
}
log.logger.info(`The commands extension size is ${extensions.size}.`);
}
extensionChangedListener(extension, action) {
this.handleCommandExtensions();
}
stop(context) {}
}
The plugin will extend the functionalities of another plugin, it will define the extensions attribute of the plugin.json. The extension and the content of it need to follow the rules of ExtensionPoint.
Below is the extensions attribute of plugin.json.
{
"id": "demoPlugin2",
"version": "1.0.0",
"dependencies": [{
"id": "demoPlugin",
"version": "1.0.0"
}],
"extensions": [{
"id": "commands",
"data": {
"name": "echo",
"command": "commands/EchoCommand.js"
}
}]
}
Below is the EchoCommand.js definition.
import { Minima } from 'minimajs';
export default class EchoCommand {
constructor() {
this.run = this.run.bind(this);
}
run() {
let demoPlugin = Minima.instance.getPlugin('demoPlugin');
let Assert = demoPlugin.loadClass('utilities/Assert.js').default;
Assert.notNull('demoPlugin', demoPlugin);
console.log('The echo command is executed.');
}
}
The extension must match the requirement of the ExtensionPoint.
The plugin can be extended by other plugins will need to listen the extension changed event and response to it. We can use the PluginContext.addExtensionChangedListener or Minima.instance.addExtensionChangedListener to listen the extension changed event.
import { Minima, Extension, ExtensionAction, PluginContext, log } from 'minimajs';
export default class Activator {
constructor() {
this.start = this.start.bind(this);
this.stop = this.stop.bind(this);
this.handleCommandExtensions = this.handleCommandExtensions.bind(this);
this.extensionChangedListener = this.extensionChangedListener.bind(this);
}
/**
* 插件入口
*
* @param {PluginContext} context 插件上下文
* @memberof Activator
*/
start(context) {
context.addExtensionChangedListener(this.extensionChangedListener);
this.handleCommandExtensions();
}
handleCommandExtensions() {
let extensions = Minima.instance.getExtensions('commands');
for (let extension of extensions) {
let Command = extension.owner.loadClass(extension.data.command).default;
let command = new Command();
command.run();
}
log.logger.info(`The commands extension size is ${extensions.size}.`);
}
extensionChangedListener(extension, action) {
this.handleCommandExtensions();
}
stop(context) {}
}
Keep in mind, you can get the details information from log.log file in the root directory of the runtime.
For bugs and feature requests, please contact me.
Lorry Chen
Have 10 years experience on the plugin framework. Expert at OSGi.
Any problems, please contact me with the QQ Group as below.
Apache License 2.0.
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
1. 开源生态
2. 协作、人、软件
3. 评估模型