1.2.0 • Published 5 years ago

hot-container v1.2.0

Weekly downloads
1
License
MIT
Repository
github
Last release
5 years ago

hot-container

Dependency injection container with hot-reloading for Node.js.

Introduction

A dependency injection container is an object which knows how to create other objects or functions. These objects or functions can be composed using other objects or functions, called dependencies.

Each object or function managed by the container is defined as a separate Node.js module, i.e. a file with a .js or .json extension, located in a specific directory. It can be either a plain Node.js module, or a special dynamic module which exports a constructor function and an array of dependencies. The dependencies are automatically resolved by the container and injected into the constructor.

When the hot-reloading feature is enabled, the container tracks file changes and automatically reloads modified modules. All objects or functions which depend on the modified modules are also automatically re-created. This is especially useful during development, making it possible to modify the application code on-the-fly without having to restart it. Hot-reloading can be disabled in production mode to reduce resource usage.

Installation

$ npm install hot-container

Usage

Creating a container

const hotContainer = require( 'hot-container' );

const container = hotContainer( {
  root: __dirname,
  dir: 'modules',
  aliases: {
    config: 'data/config.json',
    utils: 'utils'
  },
  watch: true,
  verbose: true
} );

Options:

  • root - the path of the root directory; the default value is the directory containing the current entry script
  • dir - the directory, relative to root, containing modules which are loaded by the container; the default value is '.'
  • aliases - an object which defines file and directory aliases (see the aliases section below)
  • watch - when enabled, modified modules are automatically reloaded; the default value is true
  • verbose - when enabled, debugging information is printed to the console; the default value is false

Dynamic modules

A module which depends on other modules is defined in the following way:

const deps = [ 'db', 'log' ];

function init( db, log ) {
  return function() { ... };
}

module.exports = { deps, init };

The deps array specifies the dependencies of the module. The init function is called with resolved dependencies as arguments. It should return the module instance, i.e. the object or function which can be then retrieved from the container or injected as a dependency of another module.

Static modules

The container can also load simple modules without any dependencies. In that case the module instance should be directly exported from the module:

module.exports = function() { ... };

Retrieving a module instance

Use the get( name ) method to get the instance of the specified module:

const handler = container.get( 'handler' );
if ( handler != null )
  handler( request, response, next );

If the module or one of its dependencies cannot be loaded or initialized, null is returned. See the error handling section below for information about handling errors.

Note that modules are only loaded and initialized when they are needed, i.e. when get() is called. For hot-reloading to work, make sure that get() is called on every request or event processed by the application, in order to get an up-to-date instance of the module.

Use the exists( name ) method to check if a module exists without actually loading it:

if ( container.exists( 'handler' ) {
  const handler = container.get( 'handler' );
  // ...
}

In this case get() can still return null in case of an error.

Programmatic registration

Use the register( name, instance ) method to add an object or function directly to the container:

container.register( 'foo', function() { ... } );

The registered object or function can then be used as a dependency of a dynamic module or retrieved from the container using get().

The container can be registered in itself:

conainer.register( 'container', container );

This makes it possible for modules to access the container, for example in order to retrieve an instance of another module on demand, rather than during initialization.

Error handling

Use the on( name, listener ) method to register an error handler:

container.on( 'error', err => console.error( err ) );

If no error handler is registered, the application will be aborted when an error occurs.

Destroying a module

A dynamic module can export an optional destroy() function:

let timer = null;

const deps = [ 'one', 'two' ];

function init( one, two ) {
  timer = setTimeout( function() { ... }, 1000 );
  return function() { ... };
}

function destroy() {
  if ( timer != null ) {
    clearTimeout( timer );
    timer = null;
  }
}

module.exports = { deps, init, destroy };

This function is called when the module or one of its dependencies are modified. It should clean up any external resources, such as timers, file handles, etc.

Note that a module is destroyed immediately when a modification is detected, but it's not initialized again until it's needed.

Use the destroy() method of the container to destroy all modules:

container.destroy();

Stopping the container

Use the stop() method to stop watching modified files:

container.stop();

Note that the application will keep running as long as the container is watching files.

Module metadata

A dynamic module can optionally export additional properties which describe the module:

const description = 'This is my module.';

const deps = [ 'one', 'two' ];

function init( one, two ) {
  return ...;
}

module.exports = { description, deps, init };

Use the meta( name ) function to get these metadata:

const handler = container.get( 'handler' );
const meta = container.meta( 'hanler' );
console.log( meta.description ); // prints 'This is my module.'

Note that meta() will return null is the module wasn't initialized, so you must call get() first.

Aliases

When passing options to the container, you can define aliases to access files or directories outside of the default dir directory. For example:

const container = hotContainer( {
  aliases: {
    config: 'data/config.json',
    utils: 'utils'
  }
} );

In this case, when 'config' is used as the module name, the container will load the data/config.json file. When the module name is prefixed with 'utils/', for example 'utils/log', it will load files from the 'utils' directory.

The alias paths can be absolute or relative to the root directory.

1.2.0

5 years ago

1.1.0

5 years ago

1.0.0

5 years ago