The CommonJS AMD proposal defines an elegant, simple API for declaring modules that can be used with synchronous or asynchronous script-tag based loading in the browser. RequireJS already implements this API, and Dojo will soon have full support as well. The API for defining modules is as simple as:

define(<module-name?>, <array-of-dependencies?>, <module-factory-or-object>);

This simple API can be used in a variety of different ways for different situations.

Anonymous Modules

The recommended general purpose way of writing your own modules with this API is to omit the first argument, and just include the dependencies and the module factory. A simple module can be written:

define(["math"], function(math){
  return {
    addTen: function(x){
      return math.add(x, 10);
    }
  };
});

The first argument defines the set of dependencies (as module names) for this module. The modules exported values are passed in as the arguments to the callback function once the dependent modules have been loaded.

When the module name (of the current module) is omitted, as in the example, the module is anonymous. This is a powerful way to write modules since the module source code is not coupled to any identity. The module can easily be moved to a different location without requiring any code change. This technique follows basic DRY principles, avoiding duplicate information about the identity of a module (the filename/path information does not need to be duplicated in code). This not only makes writing modules easier, but gives greater flexibility in module reuse.

Let’s look at how we load this module from a web page. We will assume the above module was put in a file called adder.js. Using RequireJS, we can load our module like this:

<script src="require.js"></script>
<script>
require(["adder"], function(adder){
  // ready to use adder
});
</script>

Once the initial entry require is called, RequireJS takes care of downloading all the dependencies of adder. We can use this same require call to load any of the module forms described below as well.

There are some rules for anonymous module usage. There may only be one anonymous module per file, and the anonymous modules must be loaded through the loader (you can just include a <script> pointing to the module).

Data Wrapping: The New JSON-P

Modules that only provide data, or dependency free methods can simply provide an object as the only argument to define():

define({
  name:"some data"
});

This is similar to JSON-P, but actually provides a significant advantage over JSON-P since it can be written in a static file without requiring the dynamic callback generation. This makes this format cacheable and CDN friendly.

CommonJS module wrapping

CommonJS modules can easily be used with the asynchronous module loader by simply wrapping them with a define call. Here you can also omit the first and second parameter, and the module’s require calls will be scanned to determine the appropriate dependencies. For example:

define(function(require, exports){
// standard CommonJS module:
  var math = require("math");
  exports.addTen = function(x){
    return math.add(x, 10);
  };
});

It is important to note that this form requires the module loader to inspect the function for require calls. The require calls must be written in the form of require("...") in order for this analysis to work properly. There are also certain browsers where this will simply not work (some versions of Opera mobile and PS3). This may not be an issue at all if your modules will be run through a build process before deployment. However, you can also wrap CommonJS modules, and manually specify dependencies. The specification allows us to also reference CommonJS variables, so we can include the standard “require” and “exports” variables:

define(["require", "exports", "math"], function(require, exports){
// standard CommonJS module:
  var math = require("math");
  exports.addTen = function(x){
    return math.add(x, 10);
  };
});

Full Module Definitions

A full module definition includes the module name, dependencies, and the factory. This form has the advantage that the module can be included in another file or it can be directly loaded with a script tag. This is the format generated by the build tools so that multiple dependencies can be combined in a single file. An example of this format:

define("adder", ["math"], function(math){
  return {
    addTen: function(x){
      return math.add(x, 10);
    }
  };
});

Finally, the last permutation of these arguments is to include the module id, but omit the dependencies list. This can be used when you want to indicate the module id (for use with direct script tag), but there are no dependencies. With no dependency array, the arguments default to “require”, “exports”, and “module”. We could alternately create our adder module:

define("adder", function(require, exports){
  exports.addTen = function(x){
      return x + 10;
  };
});

Again all these module forms can be handled by RequireJS and can be loaded by listing them as dependencies in another module or directly loading them with a require() call.

This simple API provides flexibility to declare modules for a variety of situations, from anonymous modules that can easily be moved around, to “built” modules that are ready to be used from script tags. This API is implemented by RequireJS and Dojo, as well Nodules for Node. You can also use Transporter to do on-the-fly concatenation of AMD modules and automatic wrapping of CommonJS modules that are delivered in this format.