Saturday, September 1, 2012

Modularity in JavaScript Code with RequireJS

I'm watching John Papa's excellent Single Page Web Application course from Pluralsight, and he discusses the RequireJS library in on of the lessons.

This is a really elegant way to resolve dependencies between different parts of a system in a loosely-coupled, highly-controlled way.

RequireJS Summary

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.

IE 6+ .......... compatible ✔
Firefox 2+ ..... compatible ✔
Safari 3.2+ .... compatible ✔
Chrome 3+ ...... compatible ✔
Opera 10+ ...... compatible ✔

John's Demo

In his video he uses this demo: https://github.com/johnpapa/kis-requirejs-demo.

From the Scripts3 folder:

The main work happens in, well, main.js. It  starts by configuring requirejs with some path info, then it executes a function in a specific way.

The call to the "require" function, with the array parameter then the function is telling requirejs that it needs to find a module named "alterter", and it will then pass that module into the function. I don't believe it's required that the first parameter be named "alerter", but it makes sense to do that.

If you look at the aleter.js file itself, you'll see it is created with the define function. The two array elements passed to it specific it depends on jQuery, and on dataservice. And, notice jQuery gets passed to the function as $, the standard idiom for jQuery's name.

Lastly, dataservice itself has no external dependencies, and thus it has no array of dependencies passed into it.

This is very clean way of specifying, defining, and instantiating loosely-coupled modules in JavaScript. It reminds me in some ways of the attribute-based approach used in MEF, the Managed Extensibility Framework, within .NET.

I say loosely-coupled here because, by it's nature, the dependencies are both referred to and defined as string names. Thus, you could replace a specific implementation with another simply by changing the scripts that are loaded, as long as each implementation maintains the "same interface", so-to-speak. The way JavaScript works, I imagine you could even do this at runtime by redefining what "alerter" or "dataservice" is for that matter. However, I doubt it would propagate into instances that have already had their dependencies injected.

main.js:

(function () {
    requirejs.config(
        {
            baseUrl: 'scripts3',
            paths: {
                'jquery': 'jquery-1.7.2'
            }
        }
    );
    require(['alerter'],
        function (alerter) {
            alerter.showMessage();
        });
})();

alerter.js:

define('alerter',
    ['jquery', 'dataservice'],
    function ($, dataservice) {
        var
            name = 'John',
            showMessage = function () {
                var msg = dataservice.getMessage();
                //alert(msg + ', ' + name);
                $('#messagebox').text(msg + ', ' + name);
            };
        return {
            showMessage: showMessage
        };
    });
 

dataservice.js 

define('dataservice', [],
    function () {
        var
            msg = 'Welcome to Code Camp',
            getMessage = function () {
                return msg;
            };
        return {
            getMessage: getMessage
        };
    });

No comments:

Post a Comment