Managing dependencies with RequireJS

Tuesday, September 6, 2011 Posted by Ruslan Matveev
As web applications becoming more and more mature, they inherit more and more techniques and patterns from their older brothers - desktop applications. One of the things that's been there for a while is dependency management. JavaScript itself doesn't provide such functionality out of the box, so there is no way to include file "b.js" from file "a.js" as you would do that in php or any other server side language. However there are plenty of solutions available, that will help you to organize dependency management in your JavaScript code. Today I'm gonna give you an introduction into managing dependencies with RequireJS.

RequireJS is a JavaScript file and module loader. It is optimized for in-browser use, but it can be used in other JavaScript environments, like Rhino and Node. Using a modular script loader like RequireJS will improve the speed and quality of your code.

Without going into something complex, let's firstly define, what do we really need from abstract dependency manager. Of course there are many features that we can think of, however two of them are necessary to have. So our abstract dependency manager cannot get away without ability to include and evaluate specified file or list of files and being able to do that in asynchronous mode (since we're talking about AJAX applications). So how do we do that with RequireJS?

First of all, you'll have to obtain a copy of require.js and include it into your page:

<script type="text/javascript" src="require.js"></script>

Then check your setup using following snippet:

<script type="text/javascript">
    // include and execute jQuery
    require(["http://code.jquery.com/jquery-1.6.2.min.js"], function() {
        // this code will be executed when jQuery will be loaded from CDN
        alert($().jquery);
    });
</script>

So the idea is pretty simple, every time when we need to include something, we call require function, which will load and evaluate list of the dependencies that is passed as a first argument, once all of them are loaded, callback function that is passed as a second argument will be called.

This code will result in alert box with the jQuery version (in this case 1.6.2). It works, however this is not very good idea to include something like it's shown in the example. The problem is that when the jQuery will be executed, it will pollute global namespace with it's famous "$" variable, which is not bad at all when you know that your application will be based on jQuery 1.6.2, but what if the application needs two different versions of jQuery in the same time?

RequireJS modules

RequireJS module pattern allows you to execute some JavaScript in the separate context and return the result to the caller, without polluting and overwriting global namespace.

A module is different from a traditional script file in that it defines a well-scoped object that avoids polluting the global namespace. It can explicitly list its dependencies and get a handle on those dependencies without needing to refer to global objects, but instead receive the dependencies as arguments to the function that defines the module.

You can "modularize" jQuery like so:

// somewhere in jquery.min.js
define(function() {
    // original jquery code goes here
    // return jQuery object as the result
    return jQuery.noConflict(true);
});

Note, that in the above example I use jQuery.noConflict in order to retrieve an instance of the jQuery object to the caller, instead of attaching it to the global object, which is default behavior. If you'll try to use this "modularized" version of the jQuery library, then you'll probably do it this way:

<script type="text/javascript">
    // include modified version of the jQuery
    require(["jquery.min.js"], function($) {
        // now $ will contain jQuery object
        var jQuery = $;
        alert($().jquery);
    });
</script>

So the trick is that jQuery object now only available inside of the module definition function scope. And nowhere else. Now 2 applications that is dependent on 2 different jQuery versions can safely use it, and avoid any conflicts:

<script type="text/javascript">
    // uses jQuery 1.6.2
    require(["jquery.1.6.2.js"], function($) {
        // now $ will contain jQuery object
        var jQuery = $;
        alert($().jquery);
    });
    // uses jQuery 1.5.1
    require(["jquery.1.5.1.js"], function($) {
        // now $ will contain jQuery object
        var jQuery = $;
        alert($().jquery);
    });
</script>

Problems and solutions

1. Text file dependencies?

In order to use "text file dependencies" you'll need additional plugin for the RequireJS that is called "text", you can download it here. But the ideas is:

// defines module that uses external template
define(["text!template.html"], function($myTemplate) {
    alert($myTemplate);
});

By prepending dependency path with the plugin name + "!" (text! in the above example), we give the responsibility of handling the dependency to the plugin instead of RequireJS core. There are couple of plugins available out of box, in case if you need another one, that does something very specific, you can easily write it yourself.

2. How to get current module fileName and path?

You might need such information in order for the module to resolve some paths. I'm not sure if this is an "official" way of doing that but from what I've found out by looking at RequireJS source code, that you can have some sort of "special dependencies" that is processed by RequireJS core. One of that interesting dependencies called "module":

// example shows how to retrieve module filePath
define(["module"], function($self) {
    // alert module uri
    alert($self.uri);
});

This information can be used for resolving the paths in external css file:

// example shows how to use module uri to resolve paths in css file
define(["module", "text!stylesheet.css"], function($self, $css) {
    // obtain module path
    var moduleUrl = $self.uri;
    // replace __DIR__ with module path
    $css = $css.replace(new RegExp('__DIR__', 'g'), moduleUrl);
    // create style tag and append it into the head
    $(['<style type="text/css">', $css, '</style>'].join('')).appendTo('head');
});

In the above example, we retrieve contents of "stylesheet.css" and then replace all occurrences of "__DIR__" to the actual location of the module. In this case "stylesheet.css" may look like this:

.hello-world {
    background-image: url('__DIR__/media/background.png');
}

3. How to wrap my own library / component to make it a module?

It's very simple. Following example shows how to wrap / use TrimPath template engine in your RequireJS module. First of all download TrimPath template engine, then use following code snippet to wrap template engine into the module:

// trimpath-template.js file contents
define(function() {
    // put original code in here
    // return TrimPath object as a result
    return TrimPath;
});

Next thing is to ensure that everything works as we expect, use following code snippet to check (wow it almost sounds like rap):

<script type="text/javascript">
    require(["trimpath-template.js"], function($trimPath) {
        // define our template
        // can also be loaded from the external resource (see #1)
        var templateStr = 'Hello ${name}!';
        // parse template
        var templateObj = $trimPath.parseTemplate(templateStr);
        // process template
        var resultStr = templateObj.process({
            'name': 'John'
        });
        // alert the result
        alert(resultStr);
    });
</script>

4. How to manage module paths in some better way?

Yes this might become very complicated when your modules is located in different directories, and each require call looks like this:

<script type="text/javascript">
    require([
        "/some/absolute/path/and_another_one/myModule1.js",
        "/some/absolute/path/and_another_one/myModule2.js"
    ], function($myModule1, $myModule2) {
        // do something here...
    });
</script>

So what if in the future all the modules will have to go into different directory? Global search / replace? No way, RequireJS supports special configuration object that you can pass as a first argument:

<script type="text/javascript">
    var configuration = {
        'baseUrl': '/some/absolute/path/and_another_one/'
    };
    require(configuration, [
        "myModule1.js",
        "myModule2.js"
    ], function($myModule1, $myModule2) {
        // do something here...
    });
</script>

So this code snippet above does the same as the first one, except of that you do not need to include an absolute url to the module path every time when you need it, now it's also very easy to change, since that there is only one place where an absolute url has to be replaced in case if something changes. But now we have to pass configuration object in each and every require call, can we avoid this as well?

And the answer is: yes we can do that by overriding globally defined "require" function, and rebinding it with one extra argument - configuration object. This part is a little bit more advanced than the rest of an article, so in case if you don't know what I'm talking about, please check this blog post, before looking at the following code snippet:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="require.js"></script>
        <script type="text/javascript">
            // override require function, with it's "rebinded" version
            window.require = require.bind(this, {
                // set baseUrl once and for all
                'baseUrl': '/some/absolute/path/and_another_one/'
            });
        </script>
    </head>
    <body>
        <script type="text/javascript">
            require([
                "myModule1.js",
                "myModule2.js"
            ], function($myModule1, $myModule2) {
                // do something here...
            });
        </script>
    </body>
</html>

So the idea is that we have globally defined "require" function that can take up 'till 3 arguments, and in case if three of them are present, then they are treated in following order: configuration object, dependency list, callback function. Calling bind on original "require" function with two arguments: this object and configuration object, will replace "this" object for the require function (which is not really necessary) and prepend configuration object (second argument), as the default argument to each and every "require" call, so from now on, every time when we call "require", configuration object will be passed as the first argument automatically.

UPDATE: from James Burke, author of RequireJS:

with the latest version of RequireJS there is a .config() method so that setting configuration can be done separate from the require() calls in the page

Basically that means that there is no need for .bind() anymore, and the code snippet above can be rewritten as:

<!DOCTYPE html>
<html>
    <head>
        <script type="text/javascript" src="require.js"></script>
        <script type="text/javascript">
            // apply global RequireJS configuration
            require.config({
                // set baseUrl once and for all
                'baseUrl': '/some/absolute/path/and_another_one/'
            });
        </script>
    </head>
    <body>
        <script type="text/javascript">
            require([
                "myModule1.js",
                "myModule2.js"
            ], function($myModule1, $myModule2) {
                // do something here...
            });
        </script>
    </body>
</html>

Conclusion

This article doesn't cover many other features that are available as a part of default RequireJS functionality (such as internationalization and the build process). But I'm sure that you can find all the information you need by reading RequireJS documentation, checking RequireJS blog, or asking those questions here in the comments. Please be more active, give me your feedback, comments, discussions, this will keep me motivated, and then more interesting posts will follow.

Post a Comment