Skip to main content

Plugins API

IMA.js development stack offers built-in support for plugins. Writing plugins for IMA.js is really simple. It basically comes to creating an ordinary npm package and using pluginLoader.register method to hook into IMA.js application environment using certain functions.

info

In situations where you don't need to hook into IMA.js app environment from within your plugin (you're for example just exporting some interface), you don't need call this registration method as it servers no purpose.

Plugin registration

As mentioned above, the plugin registration is done from within your npm package entry point using pluginLoader.register method:

import { pluginLoader } from '@ima/core';
import Service from './service';

pluginLoader.register('my-ima-plugin', ns => {
ns.set('my.ima.plugin.Service', Service);
});

The register method expects 2 arguments, first is name of your plugin (this is used strictly for debugging purposes, however it is required) and callback registration function which receives Namespace as one and only argument, that you can use to specify to which namespace this plugin should be bound.

Plugin bootstrap functions

The registration function can additionally return an object with additional callback functions. These allow you to further bootstrap your plugin. All are however optional, meaning you can define any combination of these or don't return anything.

import { pluginLoader } from '@ima/core';

pluginLoader.register('my-ima-plugin', ns => {
return {
initBind: (ns, oc, config) => {},
initServices: (ns, oc, config) => {},
initSettings: (ns, oc, config) => {}
}
});

initBind

initBind(ns: Namespace, oc: ObjectContainer, config: Config['bind'], isDynamicallyLoaded = false)

This function has the same interface as a function exported in bind.js of your IMA.js application and also serves the same purpose. This is the place where you would want to initialize your custom constants and bindings and assign them to the ObjectContainer.

initServices

initServices(ns: Namespace, oc: ObjectContainer, config: Config['services'], isDynamicallyLoaded = false)

Similarly to initBind, this is equivalent to a function exported by services.js file in your application.

initSettings

initSettings(ns: Namespace, oc: ObjectContainer, config: Config['settings'], isDynamicallyLoaded = false)

You can probably already see the pattern here. This function should return an object with settings, with the same structure as function in settings.js file does.

These settings are then merged with your application settings a possible conflicts are overridden with the application settings. This allows you to define defaults for your plugin, which can be easily overridden in your application.

Examples

Putting it all together, your main file in your npm package could look something like this (borrowing contents of main.js from our @ima/plugin-useragent:

import { pluginLoader } from '@ima/core';
import PlatformJS from 'platform';

import UserAgent from './AbstractUserAgent.js';
import ClientUserAgent from './ClientUserAgent.js';
import ServerUserAgent from './ServerUserAgent.js';

pluginLoader.register('@ima/plugin-useragent', () => {
return {
initBind: (ns, oc) => {
if (oc.get('$Window').isClient()) {
oc.provide(UserAgent, ClientUserAgent, [PlatformJS, '$Window']);
} else {
oc.provide(UserAgent, ServerUserAgent, [PlatformJS, '$Request']);
}
},
initServices: (ns, oc) => {
oc.get(UserAgent).init();
},
};
});

export { ClientUserAgent, ServerUserAgent, UserAgent, PlatformJS };

Dynamically imported plugins and tree shaking

When the plugin is imported dynamically and initialized lazily, you receive isDynamicallyLoaded = true as the last argument in the registration bootstrap functions. This can help you in certain situations where you need to know when the plugin was initialized.

The bootstrap process works the same way as with plugins initialized upon application startup, meaning all plugin settings are still overwritten with possible overrides in the application settings. There's however one caveat with the ObjectContainer that you need to pay attention to.

danger

When using string syntax to get certain settings in the $dependencies field:

static get $dependencies() {
return ['$Settings.myPlugin.repeatCount'];
};

constructor(repeatCount) {
this.repeatCount = repeatCount;
}

fn() {
this.repeatXTimes(this.repeatCount);
}

This won't be updated with possible plugin defaults when it get's loaded. In order to prevent this issue, you need to access whole settings object which will get updated values:

static get $dependencies() {
return ['$Settings'];
};

myFUnction(settings) {
this.settings = settings;
}

fn() {
this.repeatXTimes(settings?.myPlugin?.repeatCount);
}

Conclusion

As you can see, creating IMA.js plugin is very easy. You can always check our IMA.js-plugins monorepo to take a look at many other already existing plugins and how they're implemented, which we describe more in detail in the documentation.