Handling plugin dependencies
Because plugins are supposed to be atomic you need a way to combine them to create more complex logic in your application. This is made possible via a robust dependency system managed by the Zoram instance.
Declaring dependencies
Declaring a dependency of a plugin over another is done by passing the id of the plugin you depend on to the dependsOn
function.
import {definePlugin, dependsOn} from '@zoram/core';
export const myPlugin = definePlugin(() => {
console.log("I'm a plugin !");
dependsOn(myOtherPlugin.id)
});
export const myOtherPlugin = definePlugin(() => {
/** do some important stuff here */
})
By declaring your dependency you have instructed the application to create myPlugin
after myOtherPlugin
. Once the setup phase is over, all hooks of the registered plugins will be invoked respecting that dependency relationship.
Using plugin ids
Because zoram uses symbols internally to discriminate plugins you won't be able to pass the string you've used in definePlugin
when calling dependsOn
. You will need to use the plugin definition (returned by definePlugin
) of your dependency.
You might think it's annoying or bad DX, but it is actually at your advantage. When you declare a dependency on another plugin it's because you need that plugin to add something in the app for you to use, usually it is a service. In such a case having an import to the module that defines the plugin has multiple benefits :
- id unity : because the id is a
symbol
it is unique across the whole global context, so there is no possibility of id collision if another plugin in the application happens to be named the same (thought it should still be avoided for clarity) - typescript : because you have an import pointing to the module that defined your dependency, typescript is able to infer the type of any service that this plugin might have registered in the application, making it possible to validate types of methods or events of that service
- package dependencies : in the case that the plugin you depend on comes from another package (be it because it comes from a lib you are using or because you work in a monorepo setup) your build pipeline can make sure that the plugin you request is actually installed for your package
Beware of transitive dependency
A transitive dependency occurs when a plugin depends on another plugin indirectly, through a chain of dependencies. This is something you will encounter as soon as you have a chain of dependency longer than two plugins. But why can this be bad ?
Suppose we have:
AuthPlugin
→ depends onLoggerPlugin
LoggerPlugin
→ depends onCoreUtilsPlugin
When loading the plugins in the application their dependency will be analysed, and they will be loaded in an order that will satisfy the dependencies. CoreUtilsPlugin
will be loaded first, then LoggerPlugin
and finally AuthPlugin
.
So? Where is the issue ?
As is there is no issue, but you might have to make a modification to LoggerPlugin
down the line and replace CoreUtilsPlugin
with another plugin or remove it entirely. This is when you might see strange errors in AuthPlugin
.
Because until now all the services and logic provided by CoreUtilsPlugin
was always loaded before AuthPlugin
you might have used some of it without declaring a dependency to it and now your app is broken.
So what do we do ?
When using services from other plugins, make sure you have a direct dependency to that plugin, here you would need to add a dependency from AuthPlugin
to LoggerPlugin
if you use some of its logic, leading to :
AuthPlugin
→ depends onLoggerPlugin
andCoreUtilsPlugin
LoggerPlugin
→ depends onCoreUtilsPlugin