dynamic-plugin-framework
Dynamic plugin framework for Deno based on Javascript Modules and import() function.
Overview
This project provides a framework for defining plugins which can be dynamically discovered and imported into a running process.
Key Features
- Universal support for both Deno and browser runtimes
- Dynamic plugin import using Javascript dynamic import
- ES2015 module based
- Written in Typescript
Key Concepts
The framework’s key concepts are borrowed from the Eclipse Project’s extension framework. The key concepts are:
- A
Plugin
provides one or moreExtension
implementations. - Each
Extension
implementation declares anExtensionPoint
identifier and anExtensionFactory
via anExtensionDescriptor
. - A
HostApplication
instantiates aPluginManager
. - The
HostApplication
can register one or moreExtensionPoint
identifiers that thePluginManager
should be aware of. - The
PluginManager
scans one or morePluginRepository
implementations to find and registerPlugin
instances for anyExtensionPoint
identifiers it is aware of. - The
HostApplication
uses thePluginManager
to query for and select anExtension
for a desiredExtensionPoint
identifier. - The
PluginManager
uses the associatedExtensionFactory
to instantiate the selectedExtension
.
The following high level class diagram illustrates these relationships:
classDiagram
direction LR
class HostApplication {
}
class ExtensionPoint1 {
<<interface>>
EXTENSION_POINT_1
}
class Plugin {
<<interface>>
}
class PluginManager {
<<interface>>
}
class PluginRepository {
<<interface>>
}
class ExtensionDescriptor {
<<interface>>
}
class ExtensionFactory {
<<interface>>
}
class Plugin1 {
}
class Extension1Descriptor {
}
class Extension1Factory {
}
class Extension1 {
}
PluginRepository --> "0..*" Plugin
PluginManager --> "1..*" PluginRepository: scans
Plugin --> "1..*" ExtensionDescriptor
ExtensionDescriptor --> ExtensionFactory
PluginManager --> "0..*" Plugin : registers
PluginManager ..> ExtensionFactory : invokes
Extension1Factory ..|> ExtensionFactory
Extension1Descriptor ..|> ExtensionDescriptor
Plugin1 ..|> Plugin
Extension1 ..|> ExtensionPoint1
Plugin1 --> Extension1Descriptor
Extension1Descriptor --> Extension1Factory
Extension1Factory ..> Extension1 : creates
Extension1Descriptor ..> ExtensionPoint1: declares
HostApplication --> PluginManager
HostApplication ..> ExtensionPoint1: defines
The following sequence diagram illustrates the key steps for a HostApplication
to use a PluginManager
for discovery and registration of Plugin
instances:
%%{init: { "sequence": { "mirrorActors":false }}}%%
sequenceDiagram
HostApplication->>PluginManager:<<static import>>
Note over PluginRepository1,PluginRepository2: May use an index or scan plugins directly
HostApplication->>PluginManager:registerExtensions(EXTENSION_POINT_1)
activate PluginManager
PluginManager->>PluginRepository1:scanForExtensions(EXTENSION_POINT_1)
activate PluginRepository1
PluginRepository1-->>PluginManager:ExtensionEntry[]
deactivate PluginRepository1
PluginManager->>PluginRepository2:scanForExtensions(EXTENSION_POINT_1)
activate PluginRepository2
PluginRepository2-->>PluginManager:ExtensionEntry[]
deactivate PluginRepository2
PluginManager-->>HostApplication:ExtensionInfo[]
deactivate PluginManager
Once registration has been performed, the HostApplication
may query the
PluginManager
for Extensions
of known ExtensionPoints
and then instantiate
them:
%%{init: { "sequence": { "mirrorActors":false, diagramMarginY: 0 }}}%%
sequenceDiagram
HostApplication->>PluginManager:getRegisteredExtensions(EXTENSION_POINT_1)
activate PluginManager
PluginManager->>PluginManager:filterExtensions
PluginManager->>HostApplication:ExtensionInfo[]
deactivate PluginManager
HostApplication->>HostApplication:select Extension1
HostApplication->>PluginManager:instantiate(extension1Handle)
activate PluginManager
PluginManager->>PluginRepository1:getExtensionDescriptorFromExtensionEntry(extensionEntry)
PluginRepository1->>Plugin1:<<dynamic import>>
Plugin1-->>PluginManager:ExtensionDescriptor
PluginManager->>Extension1Factory:create()
activate Extension1Factory
Extension1Factory->>Extension1:<<new>>
Extension1Factory-->>PluginManager:Extension1
deactivate Extension1Factory
PluginManager-->>HostApplication:Extension1
deactivate PluginManager
HostApplication->>Extension1:extensionPoint1Method
activate Extension1
deactivate Extension1
As ExtensionPoints
are simply Typescript classes, for the purposes of testing
or validation, it is possible to bypass the framework altogether and import an
Extension
and use it directly:
%%{init: { "sequence": { "mirrorActors":false }}}%%
sequenceDiagram
HostApplication->>Extension1:<<static import>>
HostApplication->>Extension1:extensionPoint1Method
activate Extension1
deactivate Extension1
Examples
The following example projects are available which support execution in both a terminal and a browser:
API
The following diagram provides an overview of the Plugin
API:
classDiagram
class Plugin {
<<interface>>
pluginData: any
}
class ExtensionDescriptor {
<<interface>>
extensionPoint
extensionData: any
}
class ExtensionFactory {
<<interface>>
create(hostData: any) Extension
}
Plugin --> "1..*" ExtensionDescriptor: extensionDescriptors
ExtensionDescriptor --> ExtensionFactory: factory
The following diagram provides an overview of the PluginManager
API:
classDiagram
class PluginManager {
<<interface>>
registerExtensions(extensionPoint)
getRegisteredExtensions(extensionPoint) Set<ExtensionInfo>
instantiate(extensionHandle, hostData: Map<string, string>) Extension
}
class ExtensionInfo {
<<interface>>
extensionHandle
extensionData: Map<string, string>
pluginData: Map<string, string>
}
API docs for the library:
Development
Test: deno test -A --unstable
Lint: deno fmt
The following diagram provides an overview of the main internal classes:
classDiagram
class PluginManager {
<<interface>>
}
class DefaultPluginManager {
}
class ExtensionPointRegistry {
<<interface>>
}
class ExtensionRegistry {
<<interface>>
}
class PluginRepository {
<<interface>>
}
class UrlListPluginRepository {
}
class InMemoryExtensionRegistry {
}
class InMemoryExtensionPointRegistry {
}
PluginManager <|.. DefaultPluginManager
ExtensionPointRegistry <|.. InMemoryExtensionPointRegistry
ExtensionRegistry <|.. InMemoryExtensionRegistry
DefaultPluginManager --> ExtensionPointRegistry
DefaultPluginManager --> ExtensionRegistry
DefaultPluginManager --> "1..*" PluginRepository
PluginRepository <|.. UrlListPluginRepository
License
MIT © Flowscripter