RequireJS – Providing Structure Where None Exists

As JavaScript is transitioning from the dark ages as a language of ridicule to a respected language of it’s own, it is obvious that some of the rough edges need polishing.

There is no rougher edge than the global namespace issue and the difficulty providing encapsulation. Even for a seemingly trivial JavaScript application, it is no longer OK to just whip up some JavaScript files and assume that you can maintain the code base as it grows. The application now lives in the browser and the amount of code you need to maintain requires modularization.

There are various efforts going on to solve this problem. Some examples are AMD (Asynchronous Module Definition) and CommonJS Modules. Without taking a definite stand on the merits of either AMD or CommonJS modules, we chose RequireJS for client side modules for FeedMe. RequireJS implements the AMD specification. This choice may seem surprising since FeedMe is built with Node which uses CommonJS modules but this was more a gut decision based on nothing more than us wanting to try RequireJS out.

The rest of this post will show some very high level examples of how you use RequireJS.

index.html

Let’s first look at what a typical HTML file will look like when you use RequireJS.

<!doctype html>
<html>
<meta charset="utf-8">
<head><title>RequireJS data-main script attribute</title></head>
<body>
<p>view js console</p>
<!-- any other markup needed -->
<script data-main="app" src="../lib/require.js"></script>
</body>
</html>

We only actually load the code for RequireJS proper rather than our own code. The data-main attribute is what RequireJS will use to asynchronously load a JavaScript file called app.js. RequireJS adds the .js extension so you don’t have to. This is typically the entry point of your application.

In this case our app.js file is really simple just to demonstrate what happens.

console.log('in app.js');

If you load index.html, you will see something like following in your browser’s console

I.e. the app.js file was loaded and the log message was executed.

A module

Create mod.js

define(function() {
console.log('in mod.js!');
return "from mod.js";
});

The define function, is AMD speak for defining a module. It really takes three arguments: an id, an array of dependencies and a factory function creating the module. In mod.js, we create an anonymous module which consists of nothing more than a string and consequently has no dependencies and only the factory function is necessary. You would not normally use the id when you define your module. It is typically used by tools when optimizing a RequireJS application. E.g. combining and minifying the modules that make up the source of the application.

Modify app.js

require(['mod'], function(m) {
console.log('in app.js! Dependency mod returned: ' + m);
});

Now, in app.js we require the module ‘mod’, which RequireJS will load asynchronously. Important to note here is that there is a direct correspondence between the dependency array and the arguments to the callback function. So in our case, RequireJS will do the following:

  1. load module ‘mod’
  2. execute the ‘mod’ factory function (which returns the string “from mod.js”)
  3. executes the callback function passing the result of #2 as the first argument (m)

If you load index.html, you will see the something like the following in your browser’s console

Two modules

Let’s take a look at a more interesting application with two modules.

We create a Crisper (Crisper.js) module

define(function() {
var Crisper = function(name, url) {
console.log('Crisper ' + name + ' constructed');
this.name = name;
this.url = url;
};
Crisper.prototype.toString = function() {
return 'Crisper(' + this.name + ', ' + this.url + ')';
};
return Crisper;
});

and a renderer (renderer.js) module

define(['jquery'], function($) {
return {
render: function(crisper) {
console.log('rendering ' + crisper);
$('div').html(crisper.toString());
}
};
});

and the app (app.js)

require.config({
paths: {
jquery: '../lib/jquery-1.8.2.min'
}
});

require(['Crisper', 'renderer'], function(Crisper, renderer) {
console.log('instantiating the CEO and rendering him');
var ceo = new Crisper('Mats Henricson', 'http://www.crisp.se/konsulter/mats-henricson');
renderer.render(ceo);
});

There are a couple of new things going on here. First we have Crisper.js returning the Crisper module and renderer.js returning the renderer module.

Crisper is pretty obvious. It just returns a function to be used to instantiate JavaScript objects representing crispers. This module has no dependencies.

The renderer module is an anonymous JavaScript object with only one property, the render function and one dependency, jQuery. Since jQuery (version 1.7 and up) supports AMD you can use as you would one of your own modules. We map the module name ‘jquery’ to the specific jquery file that we have put in our lib folder, in the configuration section in app.js. So far all examples have assumed that the modules are in the same folder as the script loaded by the data-main attribute value. There is obviously configuration for using a different folder structure. jQuery is bound to the $ symbol in the factory function. Obviously it could have been bound to anything that is a valid JavaScript identifier.

The correspondence between the array of dependencies and list of arguments to the callback function is easy to see here. The ‘Crisper’ module maps to the Crisper function and the ‘renderer’ module maps to the renderer object.

If you load index.html, you will see the something like the following in your browser

and the console

If you want to check the code out it’s available at github.

3 responses on “RequireJS – Providing Structure Where None Exists

  1. Nice, really nice explanation.
    Can you explain how to injection some privacy within these modules.
    For now from app.js i can write something like:
    var ceo = new Crisper(‘Mats Henricson’, ‘http://www.crisp.se/konsulter/mats-henricson’);
    ceo.name = “Something Else”;

    How to avoid this publicity in requirejs module?

    1. John,
      I don’t think information hiding of this kind really has anything to do with RequireJS. The problem doesn’t get worse or better.
      /Daniel

Comments are closed.