# Plugins: implementing

As of v3, ScandiPWA supports frontend plugins - reusable extensions that once created can be used in any project utilizing ScandiPWA v3. These can be used to modify the functionality of almost any part of ScandiPWA.

## Watch a definitive explanation video

{% embed url="https://www.youtube.com/watch?v=TyY9CefOVnY&feature=youtu.be" %}

## Extension file structure

A ScandiPWA extension is an M2 module (composer package) with an additional directory - `scandipwa`, which contains ScandiPWA frontend-related functionality. The extension can contain any other M2 directories with backend functionality implementation. For example, the extension below has the`etc`and`Model`directories.

All directories in `scandipwa` are optional. However, following the specified structure is mandatory - the `app` and `sw` subdirectories of `scandipwa` follow the same structure as `vendor/scandipwa/source/src/(app|sw)`. These directories have the same meaning: `component` is for your extension’s components, `query` is for GraphQL queries, etc.

The `plugin` directory contains files specifying the configuration and implementation of your plugins. Details are provided below.

A correct file structure is essential for every ScandiPWA extension. Any diversity from the pattern talked about in this article can cause malfunction. ScandiPWA extension’s file structure overview:

```
📦my-awesome-extension
 ┃ ┣ 📂etc
 ┃ ┃ ┗ # ...
 ┃ ┗ 📂Model
 ┃   ┗ # ...
 ┣ 📂src
 ┃ ┗ 📂scandipwa   # Frontend-related functionality
 ┃   ┣ 📂i18n      # Additional translations
 ┃   ┣ 📂app      # Plugins and functionality for the app context
 ┃   ┃ ┣ 📂component
 ┃   ┃ ┣ 📂query
 ┃   ┃ ┣ 📂route
 ┃   ┃ ┣ 📂store
 ┃   ┃ ┣ 📂util
 ┃   ┃ ┗ 📂plugin
 ┃   ┃   ┗ 📜<name>.plugin.js # Plugin configuration and implementation
 ┃   ┗ 📂 sw       # Plugins and functionality for the Service Worker context
 ┃     ┣ 📂handler
 ┃     ┣ 📂util
 ┃     ┗ 📂plugin
 ┃       ┗ 📜<name>.plugin.js # Plugin configuration and implementation
 ┣ 📜package.json  # JS dependencies
 ┗ 📜composer.json # Composer dependencies and the PACKAGE NAME which is mandatory
```

## Creating a simple extension

1.  In a subdirectory of your package - `src/scandipwa/app/plugin`, create files for your plugins. By convention, these **must** end with `.plugin.js`

    i. Implement your plugin’s logic (see [Plugin implementation](https://docs.scandipwa.com/docs/plugin-mechanism.html#plugin-implementation))

    ii. Configure your plugin’s target (see [Plugin configuration](https://docs.scandipwa.com/docs/plugin-mechanism.html#plugin-configuration))
2. Enable your extension in scandipwa.json (see [Enabling extensions](https://docs.scandipwa.com/docs/plugin-mechanism.html#enabling-extensions))
3. Restart the frontend compilation script for the configuration to take effect. This is also necessary whenever the scandipwa.json file is changed.

## Plugin implementation

Plugins are used to alter the behavior of functions or classes. This is done by creating wrappers for existing values to control their new behavior, similarly to Magento “around” plugins.

There are 3 main types of plugins: plugins that wrap around functions, those that wrap around other properties, and those that wrap around classes.

1. _Function_ plugins act as wrappers for the function they plug into, and are called every time the original function’s call is intercepted, instead of that function. The initial function is available in the plugin-wrapper as `callback`. It is always bound to the context it originated from (`instance` for members, `context` for regular functions).
2. _Property_ plugins modify their instances’ properties during the instantiation time. These plugins are called once for each new instance of a specific class.
3. _Class_ plugins are meant to modify the classes themselves. It is possible to use them to wrap your component into some HOCs, although such an approach is not recommended. Using these plugins (which wrap around classes) enables extending the original class via inheritance and replacing the original class in the application with the modified one (this replacement happens behind the scenes just as with the other plugin types).

### _Function_ plugins

Each plugin which wraps around a function, no matter whether it is a class member or not, is a function of the following form: `function plugin(args, callback, instance) { ... }` with the following arguments’ meanings.

* `args`: an array of arguments that are passed to the function
* `callback`: the original function, or the next plugin (which also has a `callback` which also has either the original function or the next plugin etc.)
* `instance`: the instance which is the original call context of the function

Example:

```javascript
const aroundFunction = (args, callback, instance) => {
	// Use array destructuring to get specific arguments from the array
	const [foo] = args;

	console.log(`The first argument is ${foo}`)

	// Proceed to the (original callee|next plugin)
	callback(...args);
}
```

{% hint style="warning" %}
**Note:**

It is recommended to follow the naming convention for the arguments of these functions for consistency and clarity
{% endhint %}

### _Property_ plugins

Each plugin that wraps around a _**property**_ is a function of the following form: `function plugin(prop, instance) { ... }` with the following arguments’ meanings.

* `prop`: the value you are wrapping around. Similar to the `callback` in the ‘function’ plugins, either has the original property or a value returned from the other plugin(s)
* `instance`: an instance this property is a member of

```javascript
// We can wrap around any value - objects, arrays, strings...
// Example: wrapping around an object
const property = (prop, instance) => {
    return {
        ...prop, // Keep the original values
        // Let's add a new value to this object
        someAddedValue: 'new value!'
    }
}
```

### _Class_ plugins

Each plugin that wraps around a _**class**_ is a function of the following form: `function plugin(X) { ... }` with the following arguments’ meanings.

* `X`: the class you are interacting with

The described below is a not recommended approach. This is risky because it may break some classes’ properties due to the `babel-transform-class-properties` plugin’s internal mechanics.

```javascript
const addRouter = (CategoryFilterOverlay) => {
    // E.g: return the original class wrapped in a HOC
    return withRouter(CategoryFilterOverlay);
}
```

The `class` API is designed to be used as follows. Remember that you **SHOULD NEVER** copy the original code to your extend’s members, use `super` , or any other level of abstraction for that.

{% hint style="warning" %}
**Note:**

Using this to implement React lifecycle members that are missing from the initial class is not recommended. Prefer using the regular`member-function`interception to do that, it provides an opportunity for different extensions to implement them without overriding each other.
{% endhint %}

```javascript
const addBlockBelow = (CategoryFilterOverlay) => (
	class ExtendedCategoryFilterOverlay extends CategoryFilterOverlay {
		render() {
			return (
				<>
				{ super.render() }
				<div>
					Additional block!
				</div>
				</>
			)
		}
	}
)
```

## Plugin configuration

Once you have created your plugin functions, you need to specify which places you want to plug into. In order to do this, each plugin file should have a default export - a plugin configuration object.

In the plugin configuration, the following information can be specified:

1. **Namespace**: Every class and function that can be plugged into has a namespace, indicated with the `@namespace` decorator.
2. **What** aspect of the namespace you want to modify…
3. **Name**: if you are targetting a class member, you must specify its name.

### Modifying classes

* Use the `class` plugin if you want to replace the entire class with something else (see an example above). While it is technically possible to replace the class with another class entirely, this is VERY unsafe.
* Specify `member-function` plugin if you want to alter the behavior of the class’s method. E.g: plugging into `render` or `componentDidMount`.
* Specify `member-property` and a property plugin if you want to alter the value of a field of the class. E.g: plugging into `state`.
* Specify `static-member` and a property plugin if you want to modify a class’ static field.

{% hint style="warning" %}
**Note**:

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

### &#x20;Modifying other functionality

* Use the `function` plugin type if you are plugging into a function that has its own namespace and is not a part of a class.

**Position** (Optional, defaults to 100): Specifies the order in which plugins are applied. Plugins with the lower position will be called before plugins with a higher position.

{% hint style="warning" %}
**Note:**

You can create class members that do not exist in the original classes and they will be called as you’d expect writing them directly in the class. It is useful when you need some lifecycle member functions that are not present in the original class. **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 %}

### Plugin configuration object format

```java
export default {
    '<namespace>': {
        'member-function': {
            '<name>': plugin
        },
        'member-property': {
            '<name>': plugin
        },
        'static-member': {
            '<name>': plugin
        },

        'function': plugin,
        'class': plugin
    }
}
```

Where _plugin_ can be in one of the following four formats:

```javascript
// example plugin:
// const somePlugin = (args, callback, instance) => callback(...args)

// To specify a simple plugin, use:
somePlugin

// If you want to specify multiple plugins for the same namespace and target (you are not going to need this often):
[somePlugin, someOtherPlugin]

// If you want to specify a priority for your plugin to be called sooner/later than other plugins:
{
    position: 42, // defaults to 100
    implementation: somePlugin
}

// If you want to specify multiple plugins for the same namespace and target, as well as a position for each:
[
    {
        position: 42,
        implementation: somePlugin
    },
    {
        position: 1984,
        implementation: someOtherPlugin
    }
]
```

Example:

```javascript
// e.g.
// const hideMenuPlugin = (args, callback, instance) => null;

export default {
    'Component/Header/Component': {
        'member-function': {
            'renderMenu': hideMenuPlugin
        },
        'member-property': {
            'renderMap': {
                    implementation: alterRenderMapPlugin,
                    position: 101
                }
        },
        'static-member': {
            'propTypes': [
                {
                    position: 66,
                    implementation: updatePropTypesPlugin
                },
                {
                    position: 67,
                    implementation: anotherUpdatePropTypesPlugin
                }
            ]
        },
    },
    'Component/Header/Container/mapDispatchToProps': {
        'function': mapDispatchToPropsPlugin
    }
};
```

## Plugging into other plugins’ classes

ScandiPWA allows plugging into plugins’ classes, such as components, queries, etc. The plugin configuration files (`.plugin.js`) cannot be plugged into. `.plugin.js` files’ contents can only be modified by overriding them in a theme.

## Enabling extensions

1. Properly place your extension

Any ScandiPWA extension is an M2 module, hence the placement must be the same in order for the M2 backend part to work properly. The only viable place to install the extension without any additional actions is the `app/code/<vendor>/<module>` directory.

2\. Enable the backend part using the standard M2 way: `magento se:up`, if necessary also `magento mo:en <module name>` can be used.

3\. Reference the extension from your theme’s `scandipwa.json`. In this file, you can specify the path to the extensions that the theme should use. Without specifying an extension here, all of its plugins will be ignored.

The `scandipwa.json` has the following format:

```javascript
{
    // ...
    "extensions": {
        "<name>": "<P>",
        // Examples:
        "PayPal": "vendor/scandipwa/paypal-graphql",
        "GTM": "app/code/ScandiPWA/GtmGraphQl",
    }
    // ...
}
```

**Explanation**:

* `<name>` is an arbitrary name for the plugin
* `<P>` is the relative path from Magento root to the extension’s root
