Plugins: implementing
Last updated
Last updated
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.
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 theetc
andModel
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:
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)
ii. Configure your plugin’s target (see Plugin configuration)
Enable your extension in scandipwa.json (see Enabling extensions)
Restart the frontend compilation script for the configuration to take effect. This is also necessary whenever the scandipwa.json file is changed.
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.
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).
Property plugins modify their instances’ properties during the instantiation time. These plugins are called once for each new instance of a specific class.
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).
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:
Note:
It is recommended to follow the naming convention for the arguments of these functions for consistency and clarity
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
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.
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.
Note:
Using this to implement React lifecycle members that are missing from the initial class is not recommended. Prefer using the regularmember-function
interception to do that, it provides an opportunity for different extensions to implement them without overriding each other.
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:
Namespace: Every class and function that can be plugged into has a namespace, indicated with the @namespace
decorator.
What aspect of the namespace you want to modify…
Name: if you are targetting a class member, you must specify its name.
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.
Note:
If you want to plug into a class member that is an arrow function, still use member-function
, not member-property
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.
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 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.
Where plugin can be in one of the following four formats:
Example:
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.
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:
Explanation:
<name>
is an arbitrary name for the plugin
<P>
is the relative path from Magento root to the extension’s root