RequireJS/AMD Module Forms

By on November 4, 2010 12:02 am

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(, , );

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:



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.

Comments

  • Les

    Thank you for this article. I found the “Data Wrapping: The New JSON-P” section very interesting.

  • Nice article. I want to just point out that the AMD proposal is just a proposal on the CommonJS wiki. It does have more than one implementation though, and there is not yet another proposal with multiple implementations. However there are some CommonJS list participants that want to consider the problem space more, and possibly draft another proposal. However I believe the AMD proposal is the way forward and RequireJS will continue to support it.

  • That data wrapping idea is pretty sexy.

    THe last example doesn’t really make sense to me though — where does “math” come from? Did you forget the a require(“math”)?

  • @Dean: Thanks for the correction.

  • Alejandro Ojeda

    Another try implementation on the browser side:
    http://code.google.com/p/jsdev/

  • Kent Tsai

    How can I programmatically load a module during runtime? Below code snippet will produce “require: module name ‘BasicLookAndFeel’ has not been loaded yet for context: _” error.

    define([“require”, “diagram/util/Property”], function(require, Property){
    var laf;
    return {
    create: function(lafName) {
    switch(lafName) {
    case Property.DEFAULT_LAF:
    laf = require(‘BasicLookAndFeel’);
    break;
    default:
    }
    return laf;
    }
    };
    });

  • Hi Kris. My question is about Dojo and use of AMD/RequireJS for the Dojo loader. You say that Dojo will have support for this soon. Do you know if this will be included in Dojo 1.6? If so, will all Dojo modules be re-coded to this syntax and any code I write as Dojo modules need to be in the same AMD syntax?

    Thanks for the clear write-up.

  • @Bruce: All Dojo core and Dijit modules have been transformed to AMD syntax (check out to trunk to see). DojoX has not been transformed. Code that is using Dojo 1.6 does not need to be transformed, 1.6 will be completely compatible with the legacy dojo.provide/dojo.require based syntax, so you don’t have to re-code.

  • Bruce Roberts

    @Kris – thanks. I see the change in signature in the Dojo and Dijit modules. Next question: has the Dojo loader been changed to do async loading? If yes: is it using an open source loader and will my module be async loaded if I use the AMD syntax?

  • @Bruce: No, the default Dojo loader in 1.6 will still be sync. However, this new format will mean that Dojo can be used with an async loader like RequireJS, or our own future async loader (http://bdframework.org/docs/loader/loader.html, may come in 1.7).

  • just curious, do you have plan to transform dojox modules to AMD syntax as well ? I’d like to see it happens in dojox too, as well as change all the custom attributes to html5 data attributes in dojox.

  • Regarding “Dojo can be used with an async loader like RequireJS”, can you describe/indicate how this is done? I expected 1.6 to load modules async without a custom build and perhaps it is easily done.

  • Hey Kris, thank you very much for your AMD tutorial. Can you please post a full source code example? Would be nice to have because then one can assume that nothing is missing.