About plugins
A plugin is a module which adds dynamically different behaviors to the web application.
The plugins are isolated from each other. So if you don't load a plugin, its behavior will not affect to the web application, but it should not break the whole application.
A plugin requires a minimum of files:
If a plugin requires translations, it will require a translations/. If it does not have translations, this folder is not required.
index.js
It's the module output and should export the plugin.
The plugin is a class. At the constructor, the plugin will receive some helpers injected by the framework. This way the plugin will be able to use the framework's methods. API Reference.
Example
export default class ExamplePlugin {
constructor({ getConsole }) {
const { log } = getConsole();
log('The Example Plugin has been loaded');
}
}
package.json
Defines the metadata of the plugin. The metadata defines everything about the plugin (e.g., what the plugin depends on, when the plugin needs to be executed, the version of the plugin).
Example
{
"name": "@pos-web/plugin-example",
"version": "1.0.0",
"description": "This is an example plugin.",
"license": "SEE LICENSE IN LICENSE.md",
"main": "./dist/@pos-web/plugin-example.umd.js",
"browser": "./dist/@pos-web/plugin-example.umd.min.js",
"private": true,
"files": ["dist", "translations"],
"scripts": {
"build": "devkit-scripts build",
"build:dev": "devkit-scripts build --formats umd",
"build:prod": "devkit-scripts build --formats umd.min"
},
"dependencies": {
"prop-types": "15.7.2"
},
"devDependencies": {
"@pos-web/devkit-scripts": "2.0.0"
},
"peerDependencies": {
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-is": "^16.8.0"
},
"externalDependencies": {
"react": {
"global": "React"
},
"react-dom": {
"global": "ReactDOM"
},
"react-is": {
"global": "ReactIs"
}
},
"priority": 100,
"priorities": {
"handlerOne": 20,
"handlerTwo": 10
}
}
This metadata is an extension of the npm package.json.
name
It should be unique; two plugins can not have the same name. It is also the name of the package in case the plugin is published to the npm registry. So, it is possible to declare names with @scopes. npm reference.
version
The version uses Semantic Versioning (as npm). The version is a required field and should be upgraded every time a change is done at the plugin.
Upgrading the version can be automated; actually, it is already automated using this template. To automate it, it is important to follow Conventional Commits guidelines.
Lerna is used to upgrade the version. For example: lerna publish --conventional-graduate --yes
description
A short description of the plugin. npm reference.
license
You may want to provide a a kind of license for your plugin. npm reference.
main
Defines the entry point to the plugin. The path should be relative to the root of the plugin folder.
It is required if you'd like your plugin to be used when testing other plugins. Optional otherwise. It is recommended to define this entry point as a non-minified UMD or CommonJS (easier to debug when not minified). npm reference.
browser
Defines the entry point to the plugin when running at the browser. The path should be relative to the root of the plugin folder.
It is a similar field than main. But with slight differences:
-
It is required.
-
It must be defined as UMD or AMD.
-
It is recommended to be minified.
-
It is not used when testing from other plugins.
private
If you set "private": true, then the plugin will not be published to npm and will not be packed by the pack script either.
files
Describes the entries to be included when publishing the plugin.
An alternative would be to have a .npmignore file.
It works in the same way as npm.
scripts
Contains the script commands. E.g., "build": "devkit-scripts build" to compile the plugin. npm reference.
dependencies
Contains the dependencies that should be included into the plugin.
It is important to understand that including dependencies without externalizing them will increase the bundle size of the plugin. So, it is recommended whenever is possible to externalize these dependencies and expect them to be injected to your plugin from outside. More info at peerDependencies and externalDependencies.
Warning: prop-types dependency
Because of the way this dependency works. For now, it is not possible to externalize it. So, if you are using it you'll need to install it as a dependency and ensure you remove them from the bundle by using babel-plugin-transform-react-remove-prop-types.
It is already included by Devkit Scripts babel.config.js. So you do not need to take care if you are using it.
devDependencies
Contains the dependencies needed for development. E.g., @pos-web/devkit-scripts to execute command to build the plugin.
Dependencies that are required at unit tests should be also defined here. npm reference.
peerDependencies
Contains the dependencies that the plugin requires to be injected in order to work.
The dependencies described here will not be included in the bundle (when using Devkit Script build).
If your plugin will not be installed as a package by other plugin, you will only need to describe these dependencies at externalDependencies. Anyway the recommendation is to define them in both places. npm reference.
externalDependencies
Same as peerDependencies.
The difference is that peerDependencies is a built-in npm field and externalDependencies is not; it is a custom field of the SDK.
The SDK requires this field to declare how the dependency is exposed at the global object by its UMD (Universal Module Definition). So, the Devkit Script build will know how to externalize these dependencies.
An external dependency requires to specify what's the global name of the dependency. To check it, you'll find it at the dependency's UMD bundle.
Example
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
(global = global || self), (global.umdExample = factory());
}
})(this, function () {
'use strict';
var main = (log = () => console.log('UMD Example'));
return main;
});
You will find it at:
(global = global || self), (global.umdExample = factory());
Where the global name is umdExample.
Important: For now, to allow a plugin to externalize a dependency and receive it by injection, it is required to add it as an AMD Factory at pos-web.
Maybe, in the future this step will not be needed and the dependencies will be automatically loaded from a CDN like UNPKG based on the plugin's metadata.
priority
Defines the order when the plugin will be executed to allow a plugin to be executed after or before another plugin. This way a plugin can ensure the availability of an API exposed by another plugin.
Lower priority runs before higher priority. If no priority is defined it will run at the end.
Next steps: The priority may be removed in future stages of the SDK. It is confusing to determine an order based on the other plugins you are depending on. So, the priority approach probably will change to dependencies approach.
priorities
Defines the priorities for storeHelper.handleAction.
Translations
A plugin may have translations, if that's the case, the plugin needs a translations/ folder at the first level of the plugin's folder. E.g., <rootDir>/plugins/my-plugin-example/translations/
This folder contains one JSON file for each language that the plugin will support. The naming for these files should follow the ISO norm: lng-REGION. E.g., en, en-US, zh, zh-HK
Folder structure example:
Example of translations (Spanish):
{
"one": "uno",
"two": "dos",
"three": "tres"
}
Learn more at the i18n-helper documentation.