Runtime plugins

Modify source logic without rewriting the source code!

Mosaic introduces the concept of "plugins". This is a feature of extensions.

A plugin is a proxy between the original function (or object) and the function caller (object user). A plugin intercepts invocation of the original function and has control over the arguments and retrieved return value

Plugin declaration files

Also conventionally called ".plugin.js" files, are the most important part of any extension. Because that's where the plugging logic is declared!

An instruction to the plugin system in pseudocode looks as follows.

Dear Plugin System,
    intercept member of kind KIND
    with name MEMBERNAME
    of namespace NAMESPACE
    instead of it, provide P(..., Y.X, ...) to the application

In order to make this code actually do something in your application, you will need to change KIND, NAMESPACE and MEMBERNAME to values relevant for your application. The process of getting these values is described below in this guide.

Preparations

In order to declare a plugin, some preparations are necessary. First of all, an extension module must be created. You will implement your plugin there.

For the development purposes, it's recommended to have the under-development extension module installed into your project.

Then, you should create a plugin declaration file in the proper place. The guide below describes the process of implementing logic in this plugin declaration file.

Select a target

Target namespace

Each plugin has a target to "plug into". This target is determined by a namespace. Namespace should be present in the your source, which you are willing to change. If your source lacks such a namespace - put it there!

If you are willing to create

Remember to have the source module properly configured in order for namespace comments to be handled correctly by our transformation

Target kind

Plugin system should know the kind of the piece of functionality you are trying to interact with. There are several such kinds.

Currently, the following targets are available for modifications through the plugin system:

  • function - for functions declared both via regular and arrow syntax, not belonging to any class

  • member-function - for functions declared either as regular ES6 class members or as class properties (arrow functions)

  • member-property - for class members declared via class property syntax (but not for functions!)

  • static-member - for any kind of static members

Member name

For every target kind apart from function, it is necessary to determine the name of the member you are willing to interact with. All of these target kinds are related to classes, and the names of their members will be taken for this purpose.

Due to the function being the only member of its namespace, it has a reduced configuration section and does not require a member name (see in examples)

Implement proxy function

Each target kind expects a function with a different set of arguments. Below is an overview of function implementations for each proxy type. Each of these plugins solely invokes the original functionality, thus leaving the application intact.

Such a proxy function has full control over the call of the target that you are plugging into. Hence, you can modify arguments passed to it, modify the return value, decide on whether to call the original member or not (you usually are required to, see a warning below)

The callback functions seen below are bound to the initial contexts automatically.

  • An array of args from the caller

  • callback - the original function (or the next plugin)

  • context - the original context of a function

const plugin0 = (args, callback, context) => {
    return callback(...args);
}

Not calling the callback will prevent the initial logic from being called, thus overwriting it completely. Such an action makes your plugin much less compatible with any other plugins.

This is not recommended, but if critically necessary - do this only acknowledgedly and on your own risk.

Configure the plugin

In order for a plugin declaration file to have impact on the application, it should export a piece of configuration that will be consumed by the plugin system. See the structure of such a configuration in the example below.

Note, that the variables called pluginX are proxy functions described above.

/** @namespace Namespace/Of/Some/Function */
const fn = () => { ... }

/** @namespace Namespace/Of/Some/Class */
class SomeClass {
    propertyName = { ... }
    functionName() { ... }
    static staticName() { ... }
}

You can create class members that do not exist in the original classes. It is useful when you need some life-cycle member functions that are not present in the original class.

Last updated