0.1.0 • Published 5 years ago

@motiv/hedron v0.1.0

Weekly downloads
7
License
MIT
Repository
-
Last release
5 years ago

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

Micro Frontends

Assumptions

  1. Modules are built and managed by separate teams.

  2. Each module is its own collection of JS files.

Lifecycle

  1. Module: Instantiate and assign module to a globally accessible container (i.e. window['hedron_modules'])

  2. Main: Instantiate new Hedron app

  3. Main: Install all modules

To be continued...


License

MIT

Copyright (c) 2020 Motiv Studio Ltd.