# Application plugins

ScandiPWA introduces the concept of "plugins". **This is a feature of** [**ScandiPWA extensions**](https://docs.create-scandipwa-app.com/extensions/extensions).&#x20;

{% hint style="info" %}
**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.
{% endhint %}

## To create a plugin for class

{% tabs %}
{% tab title="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-are-now-able-to-proxy-these-object-types) 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](#file-structure).
4. In this file, `export default` the[ plugin options](#plugin-configuration-syntax):
   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](#proxy-function-implementation-types) 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.

{% hint style="info" %}
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.&#x20;

**Remember** to call the`callback` 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.
{% endhint %}
{% endtab %}

{% tab title="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`.

```javascript
const addBaseFields = (args, callback) => {
    return [
        ...callback(...args),
        'base_grand_total',
        'base_currency_code'
    ];
};

export default {
    'Query/Cart': {
        'member-function': {
            _getCartTotalsFields: addBaseFields
        }
    }
};
```

{% endtab %}
{% endtabs %}

## To create a plugin for a function

{% tabs %}
{% tab title="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-are-now-able-to-proxy-these-object-types) you would like to implement. In this case it is `function`.
3. Create a new plugin file following [this guidelines](#file-structure).
4. In this file, `export default` the[ plugin options](#plugin-configuration-syntax):
   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](#proxy-function-types) 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.
   {% endtab %}

{% tab title="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`.

```javascript
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
    }
};

```

{% endtab %}
{% endtabs %}

## 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:

```javascript
/* @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`

{% hint style="warning" %}

### **Heads Up!**

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

### Function declaration & assignment binding

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

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

To bind functions assignment to a namespace:

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

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

```javascript
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&#x20;

For example:

```javascript
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](#pluggable-objects). We know this object is a class declaration, therefore we are now allowed to use the [class-declaration proxy types](#you-are-now-able-to-proxy-these-object-types). We utilize `class` and `member-property` types.&#x20;

{% hint style="info" %}
The proxy types`member-function`, `member-properties`, `static-member`require 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.
{% endhint %}

### 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:

{% tabs %}
{% tab title="member-function" %}
The class method proxy implementation function takes:

* An array of **arguments** from the caller
* **Callback** - an original method&#x20;
* **Instance** - the original class instance

An example of such function is:

```javascript
(args, callback, instance) => {
    // TODO: implement
}
```

{% endtab %}

{% tab title="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:

```javascript
(member, instance) => {
    // TODO: implement
}
```

{% endtab %}

{% tab title="static-member" %}
The static class method proxy implementation function takes:

* An array of **arguments** from the caller
* **Callback** - an original method&#x20;

An example of such function is:

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

{% endtab %}

{% tab title="class" %}
The class proxy implementation function takes:

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

An example of such function is:

```javascript
(Class) => {
    // TODO: implement
}
```

{% endtab %}

{% tab title="function" %}
The function proxy implementation function takes:

* An array of **arguments** from the caller
* **Callback** - an original method&#x20;
* **Context** - the original context of a function

An example of such function is:

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

{% endtab %}
{% endtabs %}
