bolt

Getting Started

Bolt is a module-based JavaScript framework, inspired by, but at this point (intentionally) not fully compatible with the AMD specification. Bolt consists of a runtime framework, compiler and testing tool for JavaScript modules.

There are a few things that need to be understood to take full advantage of bolt modules:

Getting Bolt

From `npm`:

npm install -g boltjs

You can test the bolt command by using bolt --version

From distribution:

Grab the latest release tar from http://dist.boltjs.io/1.4.1.1/bolt-1.4.1.1.tar.gz. And install node.js v0.8+ from http://nodejs.org/ or your package manager.

Unpack the tar, and put bolt on the path. For example:

cd install-dir
curl http://dist.boltjs.io/1.4.1.1/bolt-1.4.1.1.tar.gz | tar xfz -
sudo ln -s `pwd`/bolt-*/bin/bolt /usr/local/bin/bolt

You can test the bolt command by using bolt --version

Access directly:

Modules

Concepts

The central component of a bolt project is the module.

A module is essentially a named, self-contained unit of JavaScript code, with a specified API, that may or may not depend on other modules in order to accomplish its purpose.

Modules are created by calling the define function. Define has the following signature:

define(name, dependencies, definitionFunction);

For example, a typical bolt module definition in a project might look like:

define(
  'example.bolt.Module',   // Module name (including namespace)

  [
    'example.bolt.AnotherModule',   // Dependencies on other modules
    'example.bolt.DifferentModule'
  ],

  function (AnotherModule, DifferentModule) {   // Definition function - passed dependencies in order
    // Private state

    var hello = function () {
      alert('Hello, world!');
    };

    // Public API (exposed to other modules)
    return {
      hello: hello
    };
  }
);

By convention, this module would live in the file example/bolt/Module.js, somewhere in the source directory of a project.

Naming

The first parameter to define is the namespaced module name of the newly defined module.

Bolt module names are capitalised, and follow a namespacing convention of lower-case, dot-separated segments, for example:

example.bolt.Module

In this case, the Module module is in the example.bolt namespace.

Modules are named primarily so they can be depended upon by other modules, but also so that they can be conveniently grouped for loading from different sources, as we will see later in the section on Configuration.

Dependencies

Dependencies are specified as an array of strings of module names that a given module needs to use in order to carry out its tasks. For example:

[
  'example.bolt.AnotherModule',
  'example.bolt.DifferentModule'
]

The dependency array is the second parameter to define, and may be an empty array if a module doesn't have any dependencies.

In bolt, circular module dependencies are not allowed, for example if module example.bolt.A depends on example.bolt.B, which then depends on example.bolt.A again.

The bolt runtime will throw an exception if this situation is encountered whilst initialising a project. Note that circular dependency chains can be quite long and non-obvious in some cases, however bolt will list what the circular dependency is when it complains.

Definition Function

The definition function is responsible for initialising the module, setting up any static resources it might have, and for returning a value that will be made available to other modules that depend on in it.

The definition function is the third and final parameter to define.

The function must return a value, but it need not be an object - although typically it is, containing a set of methods that represent the module's public API. For example:

function (...) {
  // ...

  return {
    api1: api1,
    api2: api2
  };
}

Another common value that is returned by the definition function is another function. This is often the case when the module is used to manufacture instances - the returned function is treated like a constructor by modules that depend upon it.

The definition function is passed the values exported by modules specified in the list of dependencies, in the order that they appear in the list. For example, given the following dependencies:

[
  'example.bolt.AnotherModule',
  'example.bolt.DifferentModule'
]

the definition function would appear as follows:

function (AnotherModule, DifferentModule) {
  // ...
}

By convention, the names given to the arguments of the definition function are the same as the dependency module names (minus the namespace), however these argument names are arbitrary and scoped to the definition function, so they can be easily changed to something else to e.g. resolve name clashes between modules in different namespaces with the same name.

Project Structure

Bolt does not depend on any specific project structure, but does have a few defaults. All defaults can be easily overridden on the command line. The following outlines a basic project structure that works well for bolt based projects.

config
|-- bolt
|   |-- bootstrap-demo.js
|   |-- bootstrap-prod.js
|   |-- demo.js
|   `-- prod.js
|
demo
|-- html
|   ` demo.html
|-- js
|   `-- example
|       `--bolt
|          `--demo
|             `-- Demo.js
|
src
|-- js
|   `-- example
|       `-- bolt
|           |-- Module.js
|           |-- AnotherModule.js
|           `-- DifferentModule.js
test
`-- js
    |-- ModuleTest.js
    |-- AnotherModuleTest.js
    `-- DifferentModuleTest.js
    

The config/bolt/prod.js (default by convention but can be specified on command line), file is the production configuration.

configure({
  sources: [
    source('bolt', 'example.bolt', '../../src/js', mapper.hierarchical),
  ]
});
    

The config/bolt/demo.js file is the demo configuration. In this case it just delegates to the production configuration, but in a more complex scenario would include its own sources.

configure({
  configs: [
    'prod.js'
  ]
});
    

The bootstrap-*.js files are generated by the bolt init command. These files are referenced by your html to initialise bolt. Bolt's runtime API can then be used to load your modules.

The demo help shows what the client code would contain to reference bolt code.

<html>
    <head>
        <script type="text/javascript" src="../../config/bolt/bootstrap-demo.js"></script>
        <script type="text/javascript">
            ephox.bolt.module.api.main('example.bolt.Module');
        </script>
    </head>
    <body>
    </body>
</html>
    

Configuration

Bolt relies on a configuration file that defines what types of files can be loaded, where they are loaded from and potentially other configurations where this information can be found. The configuration is used for both runtime loading of modules and the compilation of modules.

Concepts

configs
configs are other javascript files that are used to configure bolt, a configuration file consists of a single configure call that takes an object with three optional keys, configs, types and sources.
types
component types that are understood by bolt - these component types are able to be loaded and compiled by bolt.
sources
sources specify where to load resources from. Each source consists of a type and then a set of configuration parameters that indicate what modules can be loaded from this source and where to load them from.

Api

configure({ configs: [ path ], types: [ type ], sources: [ source ] });

A single configure call should be the only thing made in a configuration file.

configs are an array of strings which specify the path to another configuration file. Configurations are read pre-order, so sources and types specified take precedence over those read from the imported configs. Relative paths are defined relative to the file they are declared in.

types are an array of objects created by calls to the type function specified below.

sources are an array of objects created by calls to the source function specified below.

type(type, package);

The type registers with bolt a module type to be loaded. The bolt type is always implicitly loaded but a common example is the js type, which registers a custom type for loading raw javascript files as bolt modules.

type('js', 'ephox.modulator.js')

The implementation package specifies the base package for a type. It is expected to contain two modules <package>.Modulator, which is used for runtime loading of the custom module type; and <package>.Compiler, which is used for compilation of the custom module type.

source(type, arguments ...)

The source function takes the type as a string as its first argument and then a variable number of arguments that are passed to the source types implementation. The exact argument formulation is dependent on the type argument.

For the base bolt type, this is of the form:

source('bolt', namespace, path, mapper)

The namespace argument specifies the namespace prefix that is being configured by this source, i.e. only modules matching this prefix will match this source.

The path specifies the path (excluding the mapped module name) to where the modules should be loaded from. Relative paths are defined relative to the file they are declared in.

The mapper specifies a function that takes a module name and converts it to a file path (exclude the '.js') extension. This is normally in the form of one of the convenience mappers described below.

mapper.hierarchical

A convenience function that specifies a function that takes a module name and converts it to a directory structure. For example, the module example.bolt.Module would be come the path example/bolt/Module. This is how modules are commonly layed out in source control.

mapper.flat

A convenience function that specifies a function that takes a module name and converts it to a file name. This is effectively an identity function. For example, the module example.bolt.Module would be come the path example.bolt.Module. The bolt compiler's -m flag outputs modules in this structure.

mapper.constant(path)

A convenience function that is used to construct a function that always returns a constant path. For example, a call to mapper.constant('example/bolt'), would convert any module name to the path example/bolt. This is useful for referencing a compiled file where a number of modules are contained in a single file.

Runtime

In development mode the system is designed to be run behind a web server that is serving up the source tree of your project. For pure javascript projects this is normally achieved by just serving up the source with apache or nginx. For more complex projects it would involve configuring whatever application server or framework is involved to serve up the module and config source files.

Bolt generates a bootstrap file that will read a configuration and then dynamically load modules as required. This allows for a quick save-refresh development cycle without the need for compilation. Assuming the basic project structure outlined above, a call to bolt will generate the bootstrap files for your configuration:

bolt init

Note that this process only needs to happen once. Configuration is read dynamically so the bootstrap does not need to be rebuilt for each change. The default configuration can be overridden with the -c or --config flag.

Any html that needs access to javascript modules references the bootstrap file:

<script type="text/javascript" src="../../config/bolt/bootstrap-demo.js"></script>

and can then call the bolt runtime api to load modules. In most cases it is sufficient to directly invoke a main module:

<script type="text/javascript">
  ephox.bolt.module.api.main('example.bolt.Module');
</script>

Compilation

For production, bolt will compile all modules into a single file and generate a bootstrap with an inline configuration to minimise server requests.

There are a number of ways that you may want to arrange your javascript depending on if you are generating a library, building a single-page application or a multi-page application. The best way to demonstrate this is with a series of examples. All examples use the directory gen as their output location.

Produce a bolt build for a multi-page application. A compiled file will be generated for each Main module in this example.

bolt build -o gen -e src/js/**/*Main.js

Produce a bolt build for a library. A self contained script registering all modules in their namespace will be produced. This compiles in a minimal kernel so that your library does not have a dependency on bolt.

bolt build -o gen -i -g src/js/**/api/*.js

Produce a bolt build for a library to be consumed by other bolt projects, in this build we only want modules to be converted to a convenient form, no compiled output is produced.

bolt build -o gen -m src/js

These options can be used together for more complex scenarios. See bolt help build for more detail.

Note that the examples assume use of a shell with "**" glob support. This means either zsh or bash 4.x with `shopt -s globstar` set. If you are a mac user with a default bash 3.x, bolt strongly recommends you upgrade to a nicer shell, however you can use something like $(find src/js -name \*.js) to simulate glob support.

Testing

Bolt comes with testing support. Tests are defined by a test function similar in style to a define call. An example test:

test(
  'This is the test name',

  [
    'example.bolt.AnotherModule'
  ],

  function (AnotherModule) {
    var actual = AnotherModule.five();
    assert.equal(5, actual);
  }
);

This is using the assert module available on node, see http://nodejs.org/docs/latest/api/assert.html for more detail.

To run tests you specify a config that knows how to load any modules required by the tests. For the example project structure the production configuration is sufficient. To run the tests:

bolt test config/bolt/prod.js test/js/*.js