Application plugins

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

To create a plugin - means to create a programmable proxy between original function and the function caller. Plugin can modify original function arguments and return value.

To create a plugin for class

Instruction
Example
Instruction
  1. Understand which class member you would like to proxy. Retrieve this class namespace by looking at it's leading comments.

  2. Define the type of class proxy you would like to implement. If is is:

    • member-function, member-properties, static-member it requires an additional configuration object with method or a property name mapped to the proxy implementation.

    • class it expects the proxy implementation without any configuration objects.

  3. Create a new plugin file following these guidelines.

  4. In this file, export default the plugin options:

    1. Use namespace you obtained at the step 1).

    2. Use the plugin type you defined at the step 2).

    3. Pay attention to configuration option of your desired plugin type.

  5. Take a look at the reference and define a proxy implementation based on your proxy type. Make sure you are passing in the correct arguments.

  6. Implement the proxy function according to your intention.

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.

Remember to call thecallback even if the original member is not present, that will make your plugin compatible with other plugins around the same member, by calling them after your plugin finishes its work.

Example

The main purpose of this plugin is to inject additional fields base_grand_total and base_currency_code into the Cart.query.js file's class method _getCartTotalsFields return value.

  1. The class CartQuery declared in Cart.query.js is bind to a namespace Query/Cart.

  2. We plan on modifying the class method, therefore we use member-function proxy type. It is special and requires a configuration object. It requires a method name as a key.

  3. Create a new file CartQuery.plugin.js in src/plugin, pay attention to plugin post-fix.

  4. Define an export default object:

    1. Use our namespace key from the step 1) - Query/Cart.

    2. Use the plugin type from step 2) - member-function.

    3. As stated in 2) the method we intend to modify is _getCartTotalsFields therefore we declare it in the member-function configuration object.

  5. Proxy implementation function of member-function type takes following arguments: args, callback and instance. We do not need to call any other methods of the CartQuery class instance, therefore, we can omit the last argument.

  6. Call the original method passing in the original arguments to get the original return value. This is done by calling callback(...args). Then, we extend the original return value with two additional fields: base_grand_total and base_currency_code.

const addBaseFields = (args, callback) => {
return [
...callback(...args),
'base_grand_total',
'base_currency_code'
];
};
export default {
'Query/Cart': {
'member-function': {
_getCartTotalsFields: addBaseFields
}
}
};

To create a plugin for a function

Instruction
Example
Instruction
  1. Understand which function you would like to proxy. Retrieve this function namespace by looking at it's leading comments.

  2. Define the type of class proxy you would like to implement. In this case it is function.

  3. Create a new plugin file following this guidelines.

  4. In this file, export default the plugin options:

    1. Use namespace you obtained at the step 1).

    2. Use the plugin type you defined at the step 2).

  5. Take a look at the reference and define a proxy implementation based on your proxy type. Make sure you are passing in the correct arguments.

  6. Implement the proxy function according to your intention.

Example

The main purpose of this plugin is to inject a new field gtm into the file's Config.reducer.js reducer ConfigReducer initial state. We know, that the function getInitialState is responsible for that.

  1. The function getInitialState declared in Config.reducer.js is bind to a namespace Store/Config/Reducer/getInitialState.

  2. We plan on modifying the function assignment, therefore we use function proxy type.

  3. Create a new file ConfigStore.plugin.js in src/plugin, pay attention to plugin post-fix.

  4. Define an export default object:

    1. Use our namespace key from the step 1) - Store/Config/Reducer/getInitialState.

    2. Use the plugin type from step 2) - function.

  5. Proxy implementation function of function type takes following arguments: args, callback and context. We have no intention to interact with that function context, therefore, we can omit the last argument.

  6. Call the original method passing in the original arguments to get the original return value. This is done by calling callback(...args). Then, we extend the original return value with one additional filed gtm.

import BrowserDatabase from 'Util/BrowserDatabase';
const addGtmToConfigReducerInitialState = (args, callback, instance) => {
// We need to make sure the field "gtm" is an object
const { gtm } = BrowserDatabase.getItem('config') || { gtm: {} };
return {
...callback(...args),
gtm
};
};
export default {
'Store/Config/Reducer/getInitialState': {
'function': addGtmToConfigReducerInitialState
}
};

Pluggable objects

Plugins allow you to proxy any function or class of the application, which is bind to a namespace. To bind a an object to a namespace, it should have a leading comment starting with @namespace. Upon bind to a namespace one or multiple proxy types will become available.

Class declaration binding

To bind a class to a namespace, add leading comment to a class. Like this:

/* @namespace Component/AddToCart/Container */
export class AddToCartContainer extends PureComponent {
// ...
}

You are now able to proxy these object types:

  • class methods - member-function

  • class properties - member-properties

  • static methods of a class - static-member

  • class instance itself - class

Heads Up!

If you want to plug into a class member that is an arrow function, still use member-function, not member-property

Function declaration & assignment binding

To bind a function declaration to a namespace add leading comment to it. Like this:

/* @namespace Util/Validation/validateHandler */
function validateHandler(/** ... */) {
// ...
}

To bind functions assignment to a namespace:

/** @namespace Component/AddToCart/Container/mapStateToProps */
export const mapStateToProps = (state) => ({
// ...
});

You can also do it for arrow functions passed as an argument:

fetch(/** ... */).then(
/** @namespace Component/Braintree/Component/fetchThen */
() => { /** ... */ }
);

You are now able to proxy these object types:

  • function - function

File structure

The application plugin declarations must be located inside of your extension's folder src/plugin. To declare a plugin inside of this folder, you must name it with a plugin post-fix. For example: CartQuery.plugin.js.

Plugin configuration syntax

For a plugin to work, it should export default an object of configuration. The configuration object is structured the following way:

  • object keys are the namespaces you intend to apply proxy to

  • object values are objects, which define the type of proxy to apply

For example:

export default {
'Framework/Component/App/Component': {
'class': proxyImplmentations,
'member-property': {
contextProviders: proxyImplmentations
}
}
};

In this case, the object has only one key - Framework/Component/App/Component which references an object bind to this namespace. We know this object is a class declaration, therefore we are now allowed to use the class-declaration proxy types. We utilize class and member-property types.

The proxy typesmember-function, member-properties, static-memberrequire additional configuration object. This object is structured as follows:

  • object keys are the method or property names you want to provide a proxy for

  • object values are proxy function implementations

Other proxy types can be assigned to a proxy function implementation directly.

Proxy implementation syntax

The proxyImplmentations used in the example can be:

  • A function implementing a proxy

  • An object with position and implementation keys, where:

    • position is a numerical position of a plugin (default is 100)

    • implementation is a function implementing a proxy

You can also provide an array of above types.

Proxy function implementation types

Each proxy type expects a function with a different set of arguments. Here is an overview of function implementations for each proxy type:

member-function
member-properties
static-member
class
function
member-function

The class method proxy implementation function takes:

  • An array of arguments from the caller

  • Callback - an original method

  • Instance - the original class instance

An example of such function is:

(args, callback, instance) => {
// TODO: implement
}
member-properties

The class property proxy implementation function takes:

  • Member - an original value of the class property

  • Instance - the original class instance

An example of such function is:

(member, instance) => {
// TODO: implement
}
static-member

The static class method proxy implementation function takes:

  • An array of arguments from the caller

  • Callback - an original method

An example of such function is:

(args, callback) => {
// TODO: implement
}
class

The class proxy implementation function takes:

  • Class - a class you intend to modify (not an instance!)

An example of such function is:

(Class) => {
// TODO: implement
}
function

The function proxy implementation function takes:

  • An array of arguments from the caller

  • Callback - an original method

  • Context - the original context of a function

An example of such function is:

(args, callback, context) => {
// TODO: implement
}