Sharing Code Between Plugins
The basics
Most of Open Commerce’s features are implemented via plugins, so it’s common for one plugin to use functions provided by another. There are three ways for plugins to share functions:
Plugin handlers are the most flexible way to share code or any other information. They can provide dependency and sorting information, track which plugin registered a function, or include additional metadata.
Functions by type allows sharing multiple related functions. This approach does not provide the additional information and tracking that plugin handlers can.
Queries and mutations can register a single function with a particular name.
You will find all three approaches throughout the Open Commerce codebase, but due to their flexibility, plugin handlers are the recommended way to write new code.
In this documentation, we’ll describe all three approaches with example code. For more information on creating plugins, see the Build an API Plugin guide.
Plugin handlers
When you start up Open Commerce, registerPluginHandler
is called multiple times—once for every plugin that supplies it. The handler is passed registerPlugin
options from every plugin, providing information about the presence of other plugins and their configurations.
The handler is also expected to examine the options passed in from each plugin and save any required data. Any plugin can extend the registerPlugin
options by documenting data it expects to receive from each subsequent one by using registerPluginHandler
.
For example, a tax service might expect a pluginTaxService
, and ask any custom tax widget to provide pluginTaxService
as part of its registration:
Tax service handler
export function registerPluginHandlerForTaxes({ name: pluginName, taxServices: pluginTaxServices }) {
if (Array.isArray(pluginTaxServices)) {
for (const pluginTaxService of pluginTaxServices) {
taxServices[pluginTaxService.name] = { ...pluginTaxService, pluginName };
}
}
}
Note: A registerPluginHandler
function may not return a Promise. If you need to process the data asynchronously, save it temporarily. Then create and register a startup
-type function that imports the temporary data, does the async tasks, and returns a Promise. Startup functions are called immediately after registerPluginHandler
functions in the Open Commerce startup process.
To use a handler in your plugin, include a file named registration.js
that defines and exports not only registerPluginHandler
but also any related data required by other files in the plugin. For example, the reaction-catalog
plugin's registration.js
file looks like this:
registration.js
export const customPublishedProductFields = [];
export const customPublishedProductVariantFields = [];
/**
* @summary Will be called for every plugin
* @param {Object} options The options object that the plugin passed to registerPlugin
* @returns {undefined}
*/
export function registerPluginHandler({ catalog }) {
if (catalog) {
const { publishedProductFields, publishedProductVariantFields } = catalog;
if (Array.isArray(publishedProductFields)) {
customPublishedProductFields.push(...publishedProductFields);
}
if (Array.isArray(publishedProductVariantFields)) {
customPublishedProductVariantFields.push(...publishedProductVariantFields);
}
}
}
The registerPluginHandler
function looks for the catalog
key and pushes its data into exported arrays, so any files in reaction-catalog
that import customPublishedProductFields
or customPublishedProductVariantFields
will have the full list of fields as defined by all registered plugins.
To view all fields registered by a particular plugin, look for the name
key of the object. You can then access and use any data fields that you need.
The registerPluginHandler
function itself is registered using functionsByType
:
registerPluginHandler function
import { registerPluginHandler } from "./registration";
await app.registerPlugin({
label: "Catalog",
name: "reaction-catalog",
functionsByType: {
registerPluginHandler: [registerPluginHandler]
}
});
Functions by type
To provide a function to another plugin for a specific purpose, pass it to registerPlugin
in the functionsByType
list. The plugin that calls the functions must document the arguments it will provide and the expected return value and/or side effects.
For example, this plugin registers functions of type “funky”:
Funky functions
import funkyFn from "./funkyFn";
await app.registerPlugin({
label: "Cart",
name: "reaction-cart",
functionsByType: {
funky: [funkyFn]
}
});
Another plugin can then loop through and call all “funky” functions that were registered by this plugin (or any other):
Calling registered functions
for (const funkyFn of context.getFunctionsOfType("funky")) {
// eslint-disable-next-line no-await-in-loop
await funkyFn(...args);
}
Queries and mutations
If you need to register just one function, you can use context.queries
or context.mutations
instead of functionsByType
. When you use this approach, the plugin only expects to find a function with a particular name. For example, for a function named expireCarts
on context.mutations
, call context.mutations.expireCarts
.
Note: We don’t recommend adding functions this way, because it’s the most limited approach. Additionally, if more than one plugin registers a query or mutation function, the one listed last in plugins.json
will “win” and override prior registrations, and no error or warning will be thrown.
Just as with functionsByType
, the plugin that calls the function must document the arguments it will provide and the expected return value and/or side effects. You should also add your query or mutation function to your GraphQL schema or create a GraphQL resolver for it, unless you intend it to be called only by other plugins.