Helios Kernel — include() for JavaScript
Helios Kernel is an isomorphic JavaScript module loader and dependency
manager. Under isomorphic it is implied that an application or a
library based upon the Helios Kernel can run both in browser-based
environment and in Node.js natively and without
any kind of conversion. For the moment of writing, this is the only
module loader focused on being truly isomorphic.
Helios Kernel tracks the dependency graph, loads and unloads
corresponding modules dynamically in the runtime according to the
needs of different and independent parts of an application. It is
smart enough to start initializing the modules which are ready for
that, while others are still being downloaded or parsed, and to handle
some tricky problems such as circular dependencies or broken code
(reporting the problem, but still keeping the application alive). But
the key feature of Helios Kernel is
Simplicity
Helios Kernel
provides the necessary features intended to make dependency management
simple and straightforward. Syntax of a module and dependency
declaration is implemented in the classic include-style:
include('path/to/someLibrary.js');
include('../path/to/anotherLibrary.js');
init = function() {
LIB.someLibrary.sayHello();
}
A set of dependencies is listed in the module head using the
include()
function, where each call stands for a single dependency.
The only argument is the exact path to the source of the needed module
— so that it is always easy to find out the particular dependency
source location.
Module code is located inisde the init()
function declaration. It
will be issued by Helios Kernel as soon as all dependencies are
loaded.
This is how someLibrary.js
included in the module above could look:
init = function() {
LIB.someLibrary = {};
LIB.someLibrary.sayHello = function() {
console.log('Hello World!');
}
}
Helios Kernel introduces a global object called LIB
, which is a
special registry designed to store the libraries. In this example a
library object someLibrary
is declared as a property of the LIB
object. Other modules may then access the library object over the
LIB
registry.
This differs to a more common approach of objects exporting, which is
based on the module
pattern,
aims to provide the precise control over the objects created by a
library, and thus considered as a must-have nowadays and reused in
most of other loaders. However the disadvantage of such technique is
the overheads during managing
the dependency structure, introduced by an artificial coupling between
a module (library internal structure) and the exported object (library
interface).
Using Helios Kernel one may split a library into modules with the most
logical and convenient way without being linked to the objects created
by the particular modules. Moreover Helios Kernel does not apply any
restrictions on how to transfer the data between the modules or what
to perform at all inside a module body.
By convention a library stores its routines as a property of the LIB
registry, and the name of that property should match the library
name. A set of modules which the library is split into may build-up
and fill-in the library object.
This is basicly everything you need to know to start using Helios
Kernel for setting-up the dependencies in your project. This text
contains the full documentation on Helios Kernel.
Helios Kernel compared to other approaches
There is no native dependency management solution in browser
environments. To ensure that a set of JavaScript-libraries is loaded,
the libraries are usually listed within a single html-page
header. Managing the dependencies and loading order is too tricky in
this case, and gets more and more complicated along with the project
growth. To solve this problem, several script loading approaches and
libraries exist, one of which is Helios Kernel.
In Node.js there is a native dependency declaration technique — the
require()
function which implements the specifications suggested by
CommonJS group. Just like as
in most of the browser-based solutions, it introduces object
exporting, which as mentioned
more likely brings unnecessary complications to dependency management
and objects declaration. Therefore Helios Kernel pretends to be a
simplier and more flexible alternative.
Helios Kernel may be preferred because of its simplicity, or if there
is a need to to have the modules isomorphic and compatible between web
and Node.js environments. Comparing to other dependency-management
solutions, with Helios Kernel it could be more convenient to:
-
modify or refactor a list of dependencies, especially when there are
a lot of them (each dependency is just a single include()
line
referring to a module by its path, which is specified at the module
head, not inside the module body or even some external config)
-
create a compound module which should load several modules to be
used at once (such module could simply list all dependencies using
include()
, without a need to transfer other modules' routines
through the exported objects)
-
reuse "ordinary" JavaScript libraries designed to be loaded using
the <script>
tag in a html-page (module format is very simple, and
such a library could be easily converted to a module by wrapping its
code with an init()
function declaration)
How to setup a new project based on the Helios Kernel
- Download the distribution
here
and unpack it somewhere, i.e. in
helios-kernel/
directory. You may
also use npm to install Helios Kernel under
Node.js:
$ npm install helios-kernel
- Create the initial module for the project, i.e.
main.js
with the
following content:
init = function() {
console.log('Hello world!');
}
To declare the initial module dependencies, use include()
function at the
module head
- Create the web-based starting point which will load Helios Kernel
source, and then require the project initial script. Web-based
starting point could be for instance an
index.html
with the
following content:
<script src="helios-kernel/kernel.js"></script>
<script>
window.onload = function(){
kernel.require('main.js');
}
</script>
- Create the Node.js starting point which will load Helios Kernel
library, and then require the project initial script. Starting point
for Node.js could be for instance
nodestart.js
with the following
content:
require('helios-kernel/kernel.js');
kernel.require(__dirname + '/main.js');
If you have used npm
to install Helios Kernel, you may also do it
like this:
require('helios-kernel');
kernel.require(__dirname + '/main.js');
- To launch the application under web-browser, load the newly created
index.html
, or set the project directory as a web-server root. To
start the application under node, launch the newly created
nodestart.js
using Node.js:
$ node nodestart.js
How to use Kernel-compatible library with an existing project
- Download the Helios Kernel distribution
here
and unpack it somwhere. For Node.js you may also use
npm
to install
Helios Kernel:
$ npm install helios-kernel
-
Load the Helios Kernel library script kernel.js
from the
distribution using any technique suitable for your
project/environment. For a browser-based environment you could add a
<script>
tag to the head of HTML-document. For Node.js you could use
node's require()
function to load Helios Kernel (see examples in the
previous section).
-
After the Helios Kernel is loaded, you may use kernel.require()
function to load any Kernel-compatible library.
Optionally you can even convert and merge a Kernel-compatible library
using the helios-merge tool
into a plain JavaScript bundle suitable for using without Helios
Kernel.
How to create a Helios Kernel module
Module structure is explained in the first section of this
text. Basically a module consists of the two parts: the list of
dependencies in the module head using the include()
function, and
the module code inside the init()
function declaration:
include('path/to/someLibrary.js');
include('../path/to/anotherLibrary.js');
init = function() {
LIB.someLibrary.sayHello();
}
The simpliest approach to make the module data available to other
modules, is to put the data inside a library object in the LIB
registry.
Dynamical module loading
To load a module in the runtime, use kernel.require()
function. It
takes three arguments — absolute path of the module, and the two
callbacks — for a success and for a failure.
Unlike include()
which is used for declaring a dependency in a
module head, and is mostly intended to work with relative paths,
kernel.require()
only accepts specifying the absolute path. For a
web environment you may start it with the slash /
which will stand
for the domain root. To load a remote module, provide the full URL
starting with a protocol (http://...
). You may also provide an array
of paths to load several modules at once.
Second argument, the success callback, is a function which is called
after all demanded modules and their dependencies are successfully
loaded and initialized. Inside this callback you may start using the
objects provided by the requested modules.
Third argument is a failure callback which will be called in case if
some of the requested modules (or their dependencies has failed to be
loaded. Reasons could be very different (from syntax error to network
problems), therefore you must implement some reasonable fallback or
cancellation behaviour for the loading failure.
The returned value of kernel.require()
is a reservation ticket, a
special object corresponding to a single require()
act. You will
need the ticket if you wish to unload the requested modules in the
future when you don't need them anymore.
Therefore, dynamically loading a module looks like this:
var sCallback = function() {
LIB.someLibrary.doSomething();
}
var fCallback = function() {
console.log('Cannot go any further!');
}
var ticket = kernel.require(
'/path/to/library.js', sCallback, fCallback
);
After you have finished using the requested modules, you may release
the ticket by calling kernel.release()
function:
kernel.release(ticket);
This will not make the Kernel to unload the related modules
immediately, instead the Kernel will know that they are not needed
anymore at the area related to the given ticket, and will unload the
modules after they will be released everywhere else.
Module uninitializer
Upon the module unload, its code is removed from the Kernel cache. But
Kernel does not track the objects created by the module's initializer,
therefore you may provide an uninitializer function which would remove
the library objects. This function will be called during the module
unload.
Therefore the full version of a library module could look like this:
init = function() {
LIB.someLibrary = {};
LIB.someLibrary.sayHello = function() {
console.log('Hello World!');
}
}
uninit = function() {
LIB.someLibrary = null;
delete LIB.someLibrary;
}
If a module initializer declares a single compound object
encapsulating all the library routines (the recommended way,
someLibrary
in the example above), simply clearing that object
should be enough, garbage collector should (hopefully) do the rest.
How to convert an existing library to a Kernel module
If you have a library of any format, it usually defines a set of
routines which should be used from the outside later. In most of the
web-libraries which are intended to be included using the <script>
tag, a set of global objects is defined. In this case it should be
enough to wrap the library code with the init()
function
declaration. So if the original source was like this:
libraryObject = {
...
};
libraryFunction = function() {
...
};
then it should be remade like this:
init = function() {
libraryObject = {
...
};
libraryFunction = function() {
...
}
}
Now this is a Helios Kernel compatible module, and could be loaded
using the include()
function. If you load this module, the code of
library's init()
function will be issued and initialize the objects
before the init()
function code of the module which requested the
library.
For the libraries using some kind of export object, the whole code
should also be wrapped with the init()
function. To make the
exported objects, they could be globally redeclared. For instance, a
CommonJS module could looks like this:
this.someObject = {
...
};
this.someFunction = function() {
...
};
To be converted to the Helios module, it should be remade like this:
init = function() {
someLibrary = {};
someLibrary.someObject = {
...
};
someLibrary.someFunction = function() {
...
}
}
After this module is loaded, its routines could be referred to as
someLibrary.someObject
.