mosaic
  • Overview
  • Integration
    • Next.js
    • Create React App
    • Webpack
  • Getting started
    • Extensions
    • Themes
  • Tutorials
    • Mosaic + React
    • Mosaic + Webpack
  • Install an extension
    • Install a local extension
    • Install with a package manager
    • Enable or disable an extension
  • Develop an extension
    • Anatomy of an extension
    • Namespaces
    • Runtime plugins
    • Build configuration plugins
  • CRA features
    • CRACO plugins
  • Next.js features
    • Styles
    • Pages
    • Common props
  • Architecture examples
    • Router
  • Themes
    • Parent theme system
    • File shadowing
  • Experimental
    • Module preferences
    • File provisioning
  • in-depth
    • How does it work?
Powered by GitBook
On this page
  • Plugin declaration files
  • Preparations
  • Select a target
  • Target namespace
  • Target kind
  • Member name
  • Implement proxy function
  • Configure the plugin

Was this helpful?

  1. Develop an extension

Runtime plugins

Modify source logic without rewriting the source code!

PreviousNamespacesNextBuild configuration plugins

Last updated 4 years ago

Was this helpful?

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

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
const P = (...) => { ... }

export default {
    'NAMESPACE': {
        'KIND': {
            'MEMBERNAME': P
        }
    }
}

In order to make this code actually do something in your application, you will need to change , and 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 . You will implement your plugin there.

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

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

Select a target

Target namespace

If you are willing to create

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

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);
}
  • An array of args from the caller

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

  • instance - the original class instance, this of the invokable member

const plugin1 = (args, callback, instance) => {
    return callback(...args);
}
  • member - the original value of the property

  • instance - the original class instance, this of the invokable member

const plugin2 = (member, instance) => {
    return member;
}
  • An array of arguments from the caller

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

  • Class - the original class, this of the invokable member

const plugin3 = (args, callback, Class) => {
    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.

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

/** @namespace Namespace/Of/Some/Class */
class SomeClass {
    propertyName = { ... }
    functionName() { ... }
    static staticName() { ... }
}
export default {
    'Namespace/Of/Some/Function': {
        'function': plugin0
    },
    'Namespace/Of/Some/Class': {
        'member-function': {
            functionName: plugin1
        },
        'member-property': {
            propertyName: plugin2
        },
        'static-member': {
            staticName: plugin3
        }
    }
};
export default {
    'Namespace/Of/Something': {
        'function': {
            position: 100,
            implementation: plugin0
        }
    }
};

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.

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

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

Each 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.

Note, that the variables called pluginX are proxy functions .

target kind
described above
extensions
created
installed
KIND
NAMESPACE
MEMBERNAME
proper place
namespace
properly configured
transformation