Da Fish in Sea

These are the voyages of Captain Observant

Modular Javascript Development With RequireJS

| Comments

RequireJS is a micro-library focused on defining and loading Javascript ‘modules’ so that they can easily be loaded in your own or other peoples projects. Since Javascript has no compiler, the <script> tag has traditionally been the equivalent of the ‘import’ statement in compiled languages. But it has been hard to combine multiple JS libraries which may end up clobbering each others prototypes, leading to mysterious bugs. The module pattern is a well accepted pattern for preventing exposing global variables, although you still have a global variable for the module itself. With RequireJS you can reduce the number of globals even further, since modules are not assigned to global variables but are require() ‘d inside other modules.

Another benefit is performance.. loading scripts should be asynchonous for best usage of the network. Traditional <script> tags are synchronous and blocking, even if the code is not immediately required. Other approaches to loading code use XHR & eval() which makes debugging hard, and restrict to same origin (though there are workarounds). So RequireJS creates script tags dynamically, and appends them to the head of the document.

OK, how do you use it ? If you simply want to include an external script (whatever it may be), you can do this:

1
2
3
require(["some/script.js"], function () {
    //do stuff once it is loaded
});

So what is going one here is that the first argument to ‘require()’ is an array of dependencies, which are paths to Javascript files. The callback function is executed once the file has been loaded. Pretty simple. However the real power of RequireJS comes about when you also define your modules with a special define() function, which in turn declare their dependencies.. and so on, and so on, recursively. Usually, the Module Pattern is implemented with a closure function:

1
2
3
4
5
6
7
8
MyModule = (function () {
    function boo() {
        alert("boo!");
    }
    return {
        frighten: boo
    }
})();

With RequireJS you use the define() function instead of the anonymous closure:

1
2
3
4
5
6
7
8
9
10
define(['dependency/first', 'deependency/second'] , function (d1, d2) {
    //when this gets called, the dependencies are loaded and available as d1, d2
    function boo() {
        alert("boo!");
    }
    //you must return the module explicitly:
    return {
        frighten: boo
    }
});

If this was in a file called /scripts/scary.js, you could obtain an instance of the module as follows (in /index.html):

1
  <script type="text/javascript"><!--mce:0--></script>

Notice that there is no .js on the end of the filename. RequireJS will see that and look for a define() function in the file to get the module with. Note that if you omit the .js on the path, the module must be defined using the define(). Another option, which avoids using any inline script in your HTML, is to use a <script> tag with require.js as the ‘src’, and give a data-main attribute to indicate the entry point of your app.

1
 <script src="scripts/require.js"><!--mce:1--></script>

This would work if we had a script called ‘main.js’ in the scripts folder with something like this in it:

1
2
3
define(['scary'], function (scary)) {
    scary.boo();
};

Notice how the dependency is passed as an argument to the callback function. If there are more than one dependency, they will be passed as additional arguments, in the same order as the dependencies.

If you need to interact with the DOM, you should be aware that your dependencies may be loaded before the document itself is loaded, so you can use the require.ready() function which fires when the document is ready AND all dependencies have loaded…

1
2
3
4
5
6
7
define(['scary'], function (scary)) {
    require.ready(function () {
        //start app
        scary.boo();
        //do DOM stuff...
    });
};

Note that the module name is relative to the directory containing the ‘data-main’ module. Also note, require.ready() uses window.onload for browsers (mostly IE) which do not support the DOMContentLoaded event. This may be slow if you have a lot of stuff on your page.

An example of more advanced usage is that an object literal can be passed in as the first argument to the require() function, to add some additional customization, eg, setting a baseURL, or some paths which will be expanded in the names of the dependencies.

Advanced Usage: CommonJS Compatibility

CommonJS is a JS package standard, used by, eg. NPM (Node Package Manager – see previous post). Therefore it is desirable that RequireJS would grok CommonJS packages. However, you need to specify some additional configuration in you package.json for this to work, eg.

1
2
lib: 'some/dir', //the dir containing the modules, (default = lib)
main: 'script/app' //the name of the module in the lib dir which will be used when someone require() 's the packageName (default = lib/main))

NB: for the package’s modules to be loaded they must be wrapped in a define () function as below .. this can be done manually or with a script (RequireJS provides some scripts for mass conversion).

1
2
3
4
5
define(function(require, exports, module) {
    exports.boo = function () {
        alert("boo!");
    }
});

This must be exactly specified as above! (ie the parameters of the function must be ‘require’, ‘exports’, ‘module’ in that order). This seems a bit weird … how do you declare dependencies, if you must stick to this exact function signature? Well, the answer is that there is some magic at work… RequireJS looks for any ‘require()’ function calls in your module, and makes sure they are all loaded before the function is called! This is done because CommonJS has its own require() function, which is synchronous, so RequireJS essentially overrides it with its own asynchronous version. It is the ‘require’ parameter which triggers this behaviour. But what about the ‘exports’ and ‘module’ parameters ? Well, the exports argument is what will be returned when require() is called for this module. It is not necessary to return anything from the define() function, the value of ‘exports’ will be automatically returned. I’m not quite sure what the ‘module’ argument is for, as I haven’t seen it used anywhere, so its probably best to leave it alone.. perhaps it just provides backwards compatibility with CommonJS syntax? In fact, it is possible to leave out the ‘exports’ and ‘module’ arguments if you return the module explicitly from the function given to define() .. exactly as in normal non-CommonJS usage. See here.

Optimization

RequireJS provides an optimization tool, which uses UglifyJS as its compressor.

TO use it, clone or download the RequireJS source code as a sibling of your project and run the following command from the scripts (or lib, if in a CommonJS package) dir:

1
   ../../requirejs/build/build.sh name=main out=main-built.js baseUrl=.

And you will have a compressed main-built.js file with all your dependencies in it, so they can be required with a single HTTP request. Eg., you could change the script tag above to:

1
  <script src="scripts/require.js"><!--mce:2--></script>;

The RequireJS docs are excellent, and even nice to look at, so I recommend checking them out for more info, or if this becomes out of date…

API Docs

http://requirejs.org/docs/api.html