@motiv/hedron v0.1.0
Hedron.js
Version: 0.1.0
Hedron.js is a simple micro-frontend container. It supports dependency injection, event handling, filter hooks, modules, and plugins.
Hedron is dependency-free and has a small footprint of only 7.03KB (~2.04KB gzipped).
Sections
Installation
Install via npm
$ npm i @motiv/hedron
Instantiate new Hedron instance
import Hedron from '@motiv/hedron'
const app = new Hedron()
Features
Dependency Injection
Every Hedron instance has its own dependency injection container. Registered services are lazy-loaded when retrieved for the first time.
Register Service
Services can be registered with the hedron#register
method by specifying a unique service name and the service factory function.
app.register('my-service', serviceBuilder)
Service dependencies can be specified by their service name as additional arguments, in the order they are passed into the factory function.
// Service 'a' has no dependencies
app.register('a', buildA)
// Service 'a' is a dependency of service 'b'
app.register('b', buildB, 'a')
// Services 'a' and 'b' are dependencies of service 'c'
app.register('c', buildC, 'a', 'b')
Service Factory
A service factory function is a function that accepts dependencies as arguments and returns a service instance.
const buildService = function (/* dependencies */) {
return {
/* ... */
}
}
Any required dependencies of the service must be passed in as arguments of the factory function.
const buildA = function () {
return 'A'
}
const buildB = function (a) {
return { a }
}
const buildC = function (a, b) {
return { a, b }
}
Get Service
Services can be retrieved from the container using hedron#get
, passing in the service name as the first argument.
const c = app.get('c') // { a: "A", b: { a: "A" } }
Service Lazy-Loading
When a service is retrieved for the first time, Hedron will recursively trace and build its dependencies, and finally build the requested service using their respective factory functions. The service instance is cached and returned directly on subsequent requests.
If required, you can specify Hedron to rebuild the service, and all of its dependencies, by setting the rebuild
parameter in the second argument of hedron#get
.
// This will re-build service 'c' and all its dependencies.
const c = app.get('c', true)
Events
Hedron features custom event handling with support for namespacing event handlers.
Attach Event Handler
Attach an event listener using hedron#on
by specifying the event name(s) as the first argument, and handler function as the second argument.
app.on('an-event', myEventHandler)
Event handlers can be namespaced by adding a namespace preceded by a ".
" after the event name.
app.on('an-event.some-namespace', myEventHandler)
Multiple events can be specified at once by separating each event name with a space. Namespaced and non-namespaced events can be specified together.
app.on('an-event.some-namespace another-event', myEventHandler)
Emit Event
Events can be emitted using hedron#emit
by passing in the event name(s) as the first argument.
app.emit('an-event')
Optionally, an event object can be passed as a second argument.
app.emit('an-event', new CustomEvent('an-event'))
If a namespace is specified, only handlers attached with the matching namespace will be called.
app.emit('an-event.ns1')
Multiple events can be emitted simultaneously by specifying event names separated by a space.
app.emit('event-1.ns1 event-2')
Remove Event Handler
To remove an event handler, use hedron#off
by passing in the event name(s) as the first argument, and handler function as the second argument.
app.off('an-event', handler)
Note: If a handler function was initially binded under a namespace, it must be specified.
app.on('an-event.ns1', handler)
app.off('an-event', handler) // This will NOT unbind the handler.
app.off('an-event.ns1', handler) // This will unbind the handler.
An event handler can be unbinded from multiple events at once by specifying all event names, separated by a space.
app.off('event-1.my-ns event-2', handler)
Filters
The filter hook mechanism allows for cross-module "enrichment" of some arbitrary data payload before it is returned to and processed by the initial module that applied the filter. While event handlers support non-sequential function calls, filters are called sequentially, in order of priority.
Register Hook to Filter
To register a hook to a filter, use hedron#use
.
hedron#use
accepts 3 arguments: the filter name, hook function, and priority value. Unlike events, filters cannot be namespaced.
const myFilterHook = (payload) => {
return {
...payload,
myFilterHook: true
}
}
app.use('enrich-payload', myFilterHook, 50)
Hook Priority
When a filter is applied, the registered handlers are executed in ascending order of priority (1, 2, ,3, ... n). Handlers registered with the same priority will be applied in the order they were registered. Handlers registered without specifying an explicit priority will be called last.
Apply Filter
To apply a filter, use hedron#apply
and pass an initial data payload as a second argument.
const result = app.apply('enrich-data', {
/* initial data value */
})
Remove Hook from Filter
To remove a hook, use hedron#remove
. If the hook was registered with an explicit priority, it must also be specified.
app.remove('enrich-payload', myFilterHook, 50)
Modules
As an application becomes larger and more complex, modules provide a logical way to group method calls together. Loosely-coupled modules are key to building large-scale micro-frontend web applications (Real-World Application).
Writing a Module
Each module must expose both an install
and uninstall
method, which are called during installation and uninstallation respectively. During calls to install/uninstall, the Hedron instance is passed as the first argument, allowing the module to access the instance methods.
const myModule = {
install: (app) => {
app.on('some-event.my-module', myHandler)
app.use('some-filter', myHook, 100)
},
uninstall: (app) => {
app.off('some-event.my-module', myHandler)
app.remove('some-filter', myHook, 100)
}
/* additional properties */
}
Install Module
To install a module, use hedron#install
, passing in 2 arguments: a unique module name, and the module object.
app.install('my-module', myModule)
Uninstall Module
To uninstall a module, use hedron#uninstall
, specifying the name of the module.
app.uninstall('my-module')
Extending Hedron with Plugins
The global Hedron class can be extended via a straightforward plugin interface.
Using a Plugin
Plugins can be installed using Hedron#plugin
.
Hedron.plugin('my-plugin', myPlugin)
Writing a Plugin
A valid plugin must include an install
method, which takes the global Hedron
class as the first argument. Plugins can extend the Hedron class through its prototype object.
const myPlugin = {
install: (Hedron) => {
Hedron.prototype.$greeting = 'Hello there.'
}
/* additional properties */
}
Hedron.plugin('my-plugin', myPlugin)
New methods and properties will be available on subsequent Hedron instances initialized.
const app = new Hedron()
console.log(app.$greeting) // "Hello there."
Miscellaneous
Chaining Methods
With the obvious exception of hedron#get
and hedron#apply
, all methods return the Hedron instance (this
). This allows methods to be chained.
app
.register('my-service', buildMyService)
.on('some-event', myEventHandler)
.use('filter-data', myFilterHook)
.install('module-a', moduleA)
Real-World Application
Micro Frontends - extending the microservice idea to frontend development
Assumptions
Modules are built and managed by separate teams.
Each module is its own collection of JS files.
Lifecycle
Module: Instantiate and assign module to a globally accessible container (i.e.
window['hedron_modules']
)Main: Instantiate new Hedron app
Main: Install all modules
To be continued...
License
Copyright (c) 2020 Motiv Studio Ltd.
5 years ago