Retrofitting AMD

2011-12-29 00:00:00 +0000 by Alex R. Young

Let's Make a Framework is an ongoing series about building a JavaScript framework from the ground up.

These articles are tagged with lmaf. The project we're creating is called Turing. Documentation is available at turingjs.com.

Last week I looked at how AMD (Asynchronous Module Definition) works and how projects use it. This week I'm going to show how to retrofit it to an existing project, in this case our Turing framework, and hopefully you'll be able to apply this to your own reusable client-side JavaScript.

Redefining Modules

Turing's modules were structured around Immediately-Invoked Function Expressions (IIFEs), with CommonJS module loading support. AMD supports CommonJS modules, and Node can work with AMD too.

The core Turing module can be defined as an AMD module like this:

define('turing.core', [], function() {
  var turing = function() {};

  // Core functionality added here

  return turing;

A module loader is now needed to use this code in a browser. RequireJS works like this:

require(['turing.core'], function(turing) {

The methods in turing.core.js will be available once RequireJS has loaded the script.


A good reason for using AMD is dependencies can be specified and resolved by loaders. In Turing, every module depends on turing.core. That means the DOM module would have to be updated to look like this:

define('turing.dom', ['turing.core'], function(turing) {
  var dom = {};
  // DOM module defined here
  return dom;

Previously, expressing dependencies between modules wasn't possible outside of code comments. Dependencies make it easier to break down functionality into smaller reusable chunks and loaded when required.

 Loading the Entire Framework at Once

When using frameworks like jQuery, many projects need a broad selection of functionality and therefore load the entire library rather than parts of it. Rather than forcing users to use require or define invocations with a large amount of dependencies, it's possible to offer a broad selection in one file.

I've created a turing.js file that contains the following:

define('turing', ['turing.core', 'turing.dom', 'turing.anim'], function(core, dom, anim) {
  core.dom = dom;
  core.anim = anim;
  return core;

These are the modules that I've ported to work with AMD so far, but I'll add all of them under turing.js once I've finished.


What about building a monolithic, minified version of Turing? RequireJS addresses this by including the RequireJS Optimizer. This basically boils down to:

> npm install requirejs
> r.js -o app.build.js


I've started adapting Turing to work with AMD, and my early tests work with RequireJS. However, I'm finding it difficult to get builds to generate with r.js (it seems like the current release has bugs).

Although using AMD's well-defined module pattern and dependencies solves certain problems when developing reusable code, it makes it difficult to satisfy users who simply want to include a library using a script tag without a module loader like RequireJS. I've been experimenting with creating a small define function that is used when a module loader isn't available, and I'll cover that in next week's tutorial.