Chitika

November 14, 2012

JavaScript modularity with RequireJS (from spaghetti code to ravioli code)

Today I would like to describe how you can make your JavaScript code much much much better.

We know a lot about how to make our c# code much better. And we always use it.
We split out our c# code to classes, put the classes to modules, put the modules to layers, etc.

But we never do the same for our JavaScript code. And this is a big mistake.

And that's why we have a lot of Spaghetti Code inside our apps.

The main problems with function spaghetti code are:

  • It's really hard to figure out where the one block of code begins and where the other ends
  • Who's responsible for what?
  • How are we deal with global scope pollution (e.g. use the same variables in different pieces of code)
  • Low code re-use
  • Not easy to maintain, test and debug.

I'm not going to describe in details what the functional spaghetti code is, because you can find a lot of references in the Internet.
I would like to show here how to avoid it and make your code better using RequireJS.

Function spaghetti code

Let's start with a simple example of spaghetti code. (I can't say that it is really good example of horrible spaghetti code, but it is simple, so it's easy to understand and see all the changes)

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Phase 1</title>
</head>
<body>
    <div>
        <h1>Modular Demo 1</h1>
    </div>
    <div id="messagebox"></div>

    <script src="../Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>

    <script type="text/javascript">

        var baseUrl = '/api/messenger/';

        function ShowMessage(id) {
            $.ajax({
                url: baseUrl + id,
                type: 'GET',
                dataType: 'json',
                success: function(data) {
                    $("#messagebox").html(data);
                }
            });
        }

        var id = 55;
        ShowMessage(id);
        
    </script>
</body>
</html>

As you can see it's really simple example. The code sends ajax request to get a message from server and then puts this message to div container.
This code is correct and works well. And at the same time not perfect but is an example of spaghetti code.

All of your code logic (sending the request, putting a message to a div container) located in the same function.

I would like to repeat again, that it is not perfect example, but we need really simple one to better undersand how we can improve it.

JavaScript modules

First of all I would like to describe how you can use the module pattern to create a module in JavaScript.

Let's start with an example first.

        var messenger = (function ($) {
            var text = 'I am a module',
                showMessage = function() {
                    $("#messagebox").html(text);
                };

            return {
                showMessage: showMessage
            };
        })($);

That's it. You have a module - messenger.
This module has internal part:

            text = 'I am a module',

And exposes externally function:

            return {
                showMessage: showMessage
            };

So, for the other code only showMessage will be visible.
And it is really simple to use it:

            messenger.showMessage();

Also in this example our module depends on jQuery. You can see it here:

        var messenger = (function ($) {
            . . .
        })($);

As you can see it's really simple to create a module in JavaScript.

Let's see how we can re-factor our code.

Ravioli code

When you eating the ravioli you are totally sure where is the one ravioli and where is another one.
Also, to take SRP (single responsibility principle) into account we have to separate our code into modules which have only single responsibilities.

This is the result:

        var config = (function() {
            var baseUrl = '/api/messenger/';

            return {
                baseUrl: baseUrl
            };
        })();

        var dataservice = (function($, config) {
            var callApi = function (url, type, callback) {
                    $.ajax({
                        url: url,
                        type: type,
                        dataType: 'json',
                        success: function (data) {
                            callback(data);
                        }
                    });
                },
                getMessage = function (id, callback) {
                    url = config.baseUrl + id;
                    callApi(url, 'GET', callback);
                };

            return {
                getMessage: getMessage
            };
        })($, config);

        var messenger = (function ($, dataservice) {
            var showMessage = function(id) {
                dataservice.getMessage(id, function(message) {
                    $("#messagebox").html(message);
                });
            };

            return {
                showMessage: showMessage
            };
        })($, dataservice);

        (function (messenger) {
            var id = 55;
            messenger.showMessage(id);
        })(messenger);

As a result we have 4 modules. Let's describe them:

  • config module - for storing our global variables
  • dataservice module - for doing communication with the server (sending ajax requests and getting the response back)
  • messenger module - for showing a message (placing a message into containers)
  • main module - as starting point of our app

And it's really easy now to change our modules if needed, for example if we will decide to change our communication mechanism with the server, or to change our messenger to show a message into jQuery dialog.

Split out your modules

The next step is splitting your modules out into JavaScript files.
Because it is not really good solution to place your JavaScript code into HTML.

At the end you will have four JavaScript files.

config.js

        var config = (function() {
            var baseUrl = '/api/messenger/';

            return {
                baseUrl: baseUrl
            };
        })();

dataservice.js

        var dataservice = (function($, config) {
            var callApi = function (url, type, callback) {
                    $.ajax({
                        url: url,
                        type: type,
                        dataType: 'json',
                        success: function (data) {
                            callback(data);
                        }
                    });
                },
                getMessage = function (id, callback) {
                    url = config.baseUrl + id;
                    callApi(url, 'GET', callback);
                };

            return {
                getMessage: getMessage
            };
        })($, config);   

messenger.js

        var messenger = (function ($, dataservice) {
            var showMessage = function(id) {
                dataservice.getMessage(id, function(message) {
                    $("#messagebox").html(message);
                });
            };

            return {
                showMessage: showMessage
            };
        })($, dataservice);

main.js

        (function (messenger) {
            var id = 55;
            messenger.showMessage(id);
        })(messenger);

Also, we have to change our HTML to load all of these JavaScript files.

index.html

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Phase 1</title>
</head>
<body>
    <div>
        <h1>Modular Demo 1</h1>
    </div>
    <div id="messagebox"></div>

    <script src="../Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>

    <script src="config.js" type="text/javascript"></script>
    <script src="dataservice.js" type="text/javascript"></script>
    <script src="messenger.js" type="text/javascript"></script>
    <script src="main.js" type="text/javascript"></script>
   
</body>
</html>

Finally, our code looks much better now.

Loading modules in proper order

Everything almost perfect in our code, except of loading scripts in the proper order.
Honestly, we do this task, but we do it manually.
Everything changes if you decide to order your modules in HTML files in alphabetical order.

    <script src="config.js" type="text/javascript"></script>
    <script src="dataservice.js" type="text/javascript"></script>
    <script src="main.js" type="text/javascript"></script>
    <script src="messenger.js" type="text/javascript"></script>

I've just put the module main.js above messenger.js. And unfortunately it has really changed behavior of our app.
Instead of showing a message it shows an exception now.

Line: 1
Error: 'messenger' is undefined

It has been happened because we changed the order, and module main.js has been loaded before messenger.js. As a result module main.js tries to call messenger.showMessage(), but the messenger is undefined, because it has not been loaded yet.

Of course, in this simple example we can easily control the order of the four scripts, but in a real application we can manage 30, or 50 or even more modules.

And it becomes a serious problem.

In this case RequireJS can really helps.

RequireJS

The following command in the Package Manager console will install RequireJS package into your ASP.NET application:

PM > Install-Package 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.

In other words RequireJS really helps:

  • To define our modules
  • To resolve module dependencies
  • To load scripts in the proper order (and asynchronously)

So, RequireJS really helps to define a structure to the modules in a JavaScript applications.

RequireJS modules

First I would like to show how you can create modules using RequireJS.
I will use the same example as I used to show how to create a JavaScript module.

    define('messenger',
        ['jquery'],
        function ($) {
            var text = 'I am a module',
                showMessage = function() {
                    $("#messagebox").html(text);
                };

            return {
                showMessage: showMessage
            };
        }
    );

It looks as easy as a JavaScript module. But I would like to describe some differences.
The RequireJS module starts with:

    define('messenger',

Where 'messenger' is the module ID. You can use this ID if you want to reference this module in other modules.

The next line describes dependencies of this module:

       ['jquery'],

In this example our module depends on jQuery only.

And then you have to specify module body as a function.

As you can see it's really simple to create a module using RequireJS.

Using RequireJS

Let's change all of our modules.

config.js

define('config',
    [],
    function () {
        var baseUrl = '/api/messenger/';

        return {
            baseUrl: baseUrl
        };
    }
);

dataservice.js

define('dataservice',
    ['jquery', 'config'],
    function ($, config) {
        var
            callApi = function (url, type, callback) {
                $.ajax({
                    url: url,
                    type: type,
                    dataType: 'json',
                    success: function (data) {
                        callback(data);
                    }
                });
            },
            
            getMessage = function (id, callback) {
                url = config.baseUrl + id;
                callApi(url, 'GET', callback);
            };

        return {
            getMessage: getMessage
        };
    }
);

messenger.js

define('messenger',
    ['jquery', 'dataservice'],
    function ($, dataservice) {
        var showMessage = function (id) {
            dataservice.getMessage(id, function (message) {
                $("#messagebox").html(message);
            });
        };

        return {
            showMessage: showMessage
        };
    }
);

main.js

(function() {
    requirejs.config(
        {
            paths: {
                'jquery': '../Scripts/jquery-1.8.2.min'
            }
        }
    );

    require(
        ['messenger'],
        function(messenger) {
            var id = 55;
            messenger.showMessage(id);
        }
    );
})();

All of the modules look as they were before, except of main module.

In this module I have configured RequireJS to specify where RequiteJS can find the jquery module.

    requirejs.config(
        {
            paths: {
                'jquery': '../Scripts/jquery-1.8.2.min'
            }
        }
    );

And then, I've specified the start up code, which depends on messenger module:

    require(
        ['messenger'],
        function(messenger) {
            var id = 55;
            messenger.showMessage(id);
        }
    );

After all, we have to change our HTML to load our modules.

index.html

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Phase 1</title>
</head>
<body>
    <div>
        <h1>Modular Demo 1</h1>
    </div>
    <div id="messagebox"></div>

    <script data-main="main" src="../Scripts/require.js" type="text/javascript"></script>
   
</body>
</html>

Small remarks about our HTML changes.

We should not load any of our modules or jQuery, because RequireJS will handle it for us.
We have to load RequireJS only, and specify 'data-main' attribute, which tells RequireJS to load main.js script after RequireJS loads. In other words, we specify start up script in 'data-main' attribute.

And after all RequireJS does the 'magic' and loads all of our modules in proper order automatically.

As a result, our code becomes much much better now, as I promised at the beginning of this post.

That's all. And see you next time.

13 comments:

  1. If I could know RequireJs earlier, I would use it in my each web application. Thank you so much for sharing your creative ideas.

    ReplyDelete
    Replies
    1. I do my best, but I don't have a lot of time to post about everything. Thanks for your feedback.

      Delete
  2. Thank you so much, this step by step tutorial was a big help for me to setup requirejs!

    ReplyDelete
  3. Very nicely (and lucidly) done! Thanks for taking the time to share.

    ReplyDelete
  4. This was way helpful. Thankyou for taking the time to put this together.

    ReplyDelete
  5. Could you add a section about Require being asynchronous? There are some pitfalls when using jQuery and your modules depend on jQuery that people coming to this blog post should know about (say a view module).

    Thanks for sharing :)

    ReplyDelete
  6. Thanks, that is the best requirejs intro I have ever seen :) One addition, for the performace concerns, probably people want to use a CDN service for jquery, that is how ->http://requirejs.org/docs/jquery.html

    ReplyDelete
  7. Am I the only one who thinks this is madness? Your example takes 13 lines of code -- not beautiful code I agree, but very concise and readable code -- and turns it into 43 lines of harder-to-read code spread over 4 files that the browser has to download separately.

    To extend your ravioli analogy, you've replaced a small bowl of tidy spaghetti with four big bowls of ravioli and then smothered the ravioli with sauce so you can't find them any more :-)

    ...I'm kidding, of course :) A great article, thank you!

    ReplyDelete
    Replies
    1. If you only need to implement a simple scenario like I showed in this simple case, of course, I wouldn't do that. But, if we are talking about small, middle or huge SPA application and we also need to test our code then I will definitely do that.

      Delete
  8. Excellent article. Clear and precise. Just what I wanted.

    ReplyDelete
  9. thank you - this helped me really understand Requirejs with modular pattern.

    ReplyDelete
  10. Thank you, this article is the key to my understanding RequireJS. The ravioli is delicious.

    ReplyDelete