Object JavaScript – Asynchronous Module Definition (AMD)

10063_580983808600819_401360548_nIn the last few posts we showed how you can create objects in JavaScript. You can define public and private functions, methods, properties in your objects.

But what about dependencies? As we write more and more complex applications, our objects rely on other objects. There becomes a hierarchy. And if that hierarchy is not respected, if one object is run before the object that it requires is loaded, there’s problems.

Developers want to write discrete JS files and modules. We need some sort of #include/import/require. we need the ability to load nested dependencies.

And we want to be able to load our objects asynchronously.

Start with Your Module

Using JavaScript functions for encapsulation you use as the module pattern or it can be the revealing module pattern that you learned about in our previous post.


(function () {
this.myGlobal = function () {};
}());

That type of module relies on attaching properties to the global object to export the module value. The module pattern does not have a way to easily declare its dependencies. The dependencies are assumed to be immediately available when this function executes.

So if for example you are writing a viewmodel that requires Knockout and jQuery, your module just has to assume that both those libraries have already been loaded. And when (not if) it isn’t there… boom.

One fix proposed fix for this is the CommonJS.

CommonJS-style Synchronous Modules

Currently, CommonJS-style synchronous modules are very popular. For example, they are used in Node.js. Such a module looks as follows.


// Import new modules anywhere
var otherModule = require("libs/otherModule");
// Export your functions, classes, objects etc.
exports.myFunction = function () {
otherModule.otherExportedFunction() + 1;
};

With this approach, the CommonJS group was able to work out dependency references and how to deal with circular dependencies, and how to get some properties about the current module.

Let’s take that concept a to the next step – asynchronous loading and module definitions.

Asynchronous Module Definition API

The Asynchronous Module Definition (AMD) API specifies a mechanism for defining modules such that the module and its dependencies can be asynchronously loaded. This is particularly well suited for the browser environment where synchronous loading of modules incurs performance, usability, debugging, and cross-domain access problems.

You define a module by including its name before your object. This specification reserves the global variable define so you can define your module.

For example:


// Import all modules in a single location
define([ "libs/otherModule" ],
function (otherModule) {
// Export your module as an object
return {
myFunction: function () {
otherModule.otherExportedFunction() + 1;
}
};
}
);

The main advantages are:

  • Asynchronous: AMDs have been designed to support asynchronicity and most AMD-compatible script loaders take advantage of it. Thus, AMD modules can be loaded in parallel. Once the last imported module has finished loading, the importing module can be evaluated. It is ironic that the Node.js module system is synchronous.
  • Work in browsers: Browser modules have to be asynchronous and AMDs are.
  • No globals: AMDs avoid global variables from being created, because there is always a wrapping function. Note, though, that CommonJS-compliant module loaders usually also (invisibly) wrap modules they load in a function.
  • No more namespacing: AMDs obviate the need for namespacing such as foo.bar.myModule, via a global variable and nested objects. The namespacing is in the path to the file, a local variable is short and convenient handle.

define()

In AMD, define is a global function that takes the following arguments:

  • id. The first argument is a string literal that names your module.
  • dependencies. The second argument, dependencies, is an array literal of the module ids that are dependencies required by the module that is being defined.
  • factory. The third argument, factory, is a function that should be executed to instantiate the module or an object.

Where Do I Get AMD?

AMD already has good adoption on the web:

Give an AMD loader a try. You have some choices:

If you want to use AMD but still use the load one script at the bottom of the HTML page approach:

AMD Goodness

AMD modules require less tooling, there are fewer edge case issues, and better debugging support.

What is important: being able to actually share code with others. AMD is the lowest energy pathway to that goal.

References

Asynchronous Module Definition (AMD) API for JavaScript

The power of the Asynchronous Module Definition

Why AMD? from the Require.JS documentation