Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Modular Javascript Using Require.Js

0.00/5 (No votes)
2 Jul 2013 3  
A look at creating well structured modular javascript using AMD library Require.js

Download demo project : RequireJsDemos.zip

Introduction 

I don't know how many of you use lots of JavaScript libraries, but we found ourselves using quite a lot within certain projects. One of the strange issues that came about from linking JavaScript files was that we occasionally ran into dependency issues, where by a particular JavaScript source file would have a dependency on another JavaScript source file that was not loaded (and therefore not available) yet.

As luck would have it there is an excellent library for dealing with interlinked JavaScript dependencies and module loading which is called : RequireJs

 

The Problem

You may be asking what problem RequireJs is attempting to solve. Well lets consider a most common example, which is that of a custom jQuery plugin, where jQuery MUST be loaded before the plugin will work. If we stuck to using standard script tags something like this

<script src="scripts/myCooljqueryPlugins.js" type="text/javascript"></script>
<script src="scripts/jquery.js" type="text/javascript"></script>

We instantly have a problem, our custom jQuery plugins will not work, as jQuery itself has not be loaded yet. Sure we could move the jQuery script tag to be before the  custom jQuery plugins, which would indeed fix the problem this once. This is however a very isolated small use case, which is not really the norm on large scale applications, where we may have many many related files which all have dependencies. This is where RequireJs can help. Want to know more, read on.

 

A Possible Solution

So now that you know the problem what is a possible solution? Well as I have already stated this article will use RequireJs a one viable solution to the problem just described. RequireJs does a bit more than just solve dependencies, it also provides asynchronous module and file loaded. In my mind the fact that RequireJs uses modules is a very good thing, as it gives a guiding push to the developer, which should nudge them into creating a module which has a single responsibility which is never a bad thing. Using RequireJs we are able to specify things like:

  • Define a module
  • Require a module 
  • The modules dependencies (via a RequireJs config called  shim)
  • The module paths 

  

The Demos

This section will outline several different RequireJs examples, starting with a typical simple use case, and then building on to slightly more complex examples.

 

Simple Demo

This is the simple basic 101 sample, and it illustrates the very basics of using RequireJs. This will be expanded upon later, but it does illustrate a few key points that we will come across in later examples.

Here is what the structure of this project looks like in Visual Studio:

 

Bootstrapping an app to use Require.js

One of the things you must do with RequireJs is to tell it which file will be used for the overall configuration. Here is a simple example

<head>
    <title>My Sample Project</title>
    <!-- data-main attribute tells require.js to load
            scripts/main.js after require.js loads. -->
    <script data-main="scripts/main" src="scripts/require.js"></script>
</head>

You can see that the data-main attribute points to a main file which is what sets up the RequireJs configuration. Lets see a simple example of this shall we:

(function() {

    require(["app/data", "helper/util", "helper/logger"], function (data, util, logger) {
        //This function is called when scripts/helper/util.js is loaded.
        //If util.js calls define(), then this function is not fired until
        //util's dependencies have loaded, and the util argument will hold
        //the module value for "helper/util".
        alert('util module : ' + util.doubleItMethod(10));


        //This function is called when scripts/app/data.js is loaded.
        //If data.js calls define(), then this function is not fired until
        //data's dependencies have loaded, and the util argument will hold
        //the module value for "app/data".
        alert('data module [0] : ' + data.storedData[0]);
        alert('data module [1] : ' + data.storedData[1]);

        logger.logThePair();
    });

})();

What we can see there is the use of the require() function, which is what RequireJs gives us. The 1st parameter (which may be an array as shown) specifies the dependencies that this function has. Lets now focus our attention on one of those modules that we have a dependency on.

This following snippet is the logger module, and note above how we state that we require() the logger module, which RequireJs gives us, and then are free to use the modules code. Which again can be seen above where we use the logger modules logThePair( ) function

define(function (require) {
    var pair = require(".././app/keyValuePairs");

    return {
        logThePair: function () {
            alert("color: " + pair.color + ", size: " + pair.size);
        }
    };
});

In this example we define a module using define() and we also take in RequireJs which gets special treatment, where RequireJs knows what to do with it. We are now able to use this defined module using another require() call somewhere else, which we saw above when we wrote out

require(["app/data", "helper/util", "helper/logger"], function (data, util, logger) {
....
}

One thing of note there is the path is also included "helper/logger" so require will look for a file that matches that path and use that. We can also specify paths like this

define(function (require) {
    var pair = require(".././app/keyValuePairs");

    return {
        logThePair: function () {
            alert("color: " + pair.color + ", size: " + pair.size);
        }
    };
});

We will see more on path configuration later

 

Using require () vs. define()

We can use both require() and define() to load module dependencies. The require() function is used to run immediately, where as define() is used to define modules which may be used from multiple locations.

 

 

Using With jQuery

So now that we have seen a trivial example of using RequireJs lets continue our journey. Lets see what it takes to create an example where we have actually library depenencies, such as a jQuery plugin that needs jQuery in order for function.

Here is what the structure of this project looks like in Visual Studio:

As before we start (as we always will) by telling RequireJs the main javascript code file to boostrap the app

<!DOCTYPE html>
<html>
    <head>
        <title>jQuery+RequireJS Sample Page</title>
        <script data-main="scripts/app" src="scripts/lib/require.js"></script>
    </head>
    <body>
        <h1>jQuery+RequireJS Sample Page</h1>
        <input type="text" maxlength="150" id="txt" />
        <input type="button" id="btn" value="click me" />
    </body>
</html>

Where the scripts/app javascript file looks like this

// Place third party dependencies in the lib folder
//
// Configure loading modules from the lib directory,
// except 'app' ones, 
requirejs.config({
    "baseUrl": "scripts/lib",
    "paths": {
      "app": "../app"
    },
    "shim": {
        "jquery.appender": ["jquery"],
        "jquery.textReplacer": ["jquery"],

    }
});

// Load the main app module to start the app
requirejs(["app/main"]);

This time we can see something new. We can see that we are actually configuring RequireJs for certain things, such as:

  1. BaseUrl : Specifies the base path for all the scripts, you can still use RequireJs with relative paths, but this is its base path
  2. Paths : Is a map of named paths where we specify a well known name and a path
  3. Shim : Is a map of files, and their dependecies. What this does is that is gives a hint to RequireJs about the required module dependencies such that RequireJs will then know to load them in the correct order. Remember that RequireJs uses a technique which means modules are loaded asychronously. It can be seen from the example above that "jquery.appender" and "jquery.textReplacer" are dependent on jQuery. As such RequireJs will load that first.It may seem that we are just swapping the evils of having the correct javascript imports in the html in the correct order for this, but what we do get from RequireJs is that modules are loaded asychronously

The last point is that we then kick of the whole app by telling RequireJs wihch is the initial app file that should be run. This is done using the line

// Load the main app module to start the app
requirejs(["app/main"]);

So lets examine that file for a minute

require(["jquery", "jquery.appender", "jquery.textReplacer"], function ($) {
    //the jquery.appender.js plugin have been loaded.
    $(function () {
        $(document).ready(function () {
            $('#btn').click(function () {
                var old = $('#txt').val()
                var rev = reverseString(old);
                $('h1').replaceTextWithFade('Old text was : ' + old);
                $('body').appendData(rev);
            });
        });
    });

    function reverseString(str) {
          if(typeof str !== 'string') {
            throw new Error('reverseString(str) accepts only strings.');
            return;
        }
    
        var strArr = str.split('');
        var reversed = strArr.reverse();
        return reversed.join('');
    };
});

It can be seen that is uses jQuery and the 2 custom jQuery plugins, that have by magic had their dependency on jQuery satisfied by RequireJs, so we can now safely use them within the javascript that asked for them as requirements.

Just for completeness here is one of the custom jQuery plugins that the code above makes use of

(function ($) {
    $.fn.appendData = function (data) {
        this.each(function () {
            return this.append('<p>' + data + '</p>');
        });
    }
}(jQuery));

RequireJs magically makes sure that this is not loaded until jQUery itself is loaded, at which poit it will be provided to these plugins

 

 

Using With ASP .NET MVC (4)

I also wanted to demonstrate how to use RequireJs within a ASP .NET MVC application (I chose MVC 4). and here is how I did it.

Here is what the structure of this project looks like in Visual Studio:

 

I used the same boostrapper "app.js" code that we saw above, and the same 2 custom jQuery plugins, that we just saw.

So now that you know are using some of the same stuff we just saw, lets proceed to the ASP .NET MVC 4 specific bits shall we.

It starts with the Bundle.Config file which for me is as follows;

public class BundleConfig
{
    public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/lib").Include(
                    "~/Scripts/Lib/require.js"));

        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
    }
}

And then we have a master layout page which makes use of the bundles like this:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <meta name="viewport" content="width=device-width" />
        @Styles.Render("~/Content/css")
        @Scripts.Render("~/bundles/lib")
        <script type="text/javascript" src="~/Scripts/app.js"></script>
        @RenderSection("scripts", required: false)
    </head>
    <body>
	.....
	.....
	.....
    </body>
</html>

However this time since we are relying on ASP .NET MVC 4s bundling capabilities we need to provide a separate link to the boostrapper for RequireJs, which is done as follows:

<script type="text/javascript" src="~/Scripts/app.js"></script>

Where app.js is the same as the previous example.

From there all that is left to do is to declare what scripts you want per page (which is typical for any ASP .NET MVC app)

@{
    ViewBag.Title = "Home Page";
}
....
....
....
....
@section Scripts {
    <script src="~/Scripts/Controller/Home/Index.js"></script>
}
....
....
....
....

Which we can then create a standard javascipt file such as the following

require(["jquery", "common/Utils", "jquery.appender", "jquery.textReplacer"], function ($, 

common) {

    $(document).ready(function () {

        $('#mainDiv').append('<p>Index page is working with Require.js</p>');
        alert('Type some text into the text box and then click the button');

        $('#btn').click(function () {
            var old = $('#txt').val()
            var rev = common.reverseString(old);
            $('#appendToDiv').appendData(rev);
        });
    });
});

Which is specific for the controller/page combo and will be injected into the "Scripts" section of the layout page.

 

Using With TypeScript

Typescript is the new kid on the block and is Microsofts attempt at proding OO like features to JavaScript. Typescript essentially has its own compiler which take a Typescript file (*.ts) and compiles that to a JavaScript file (*.js). I thought for completeness I should also cover Typescript, though this only covers how to use RequireJs with Typescript.

Here is what the structure of this project looks like in Visual Studio:

Before we start I should mention that when working with Typescript you MOST definately want to install the Web Essential Visual Studio Extension which makes working with Typescript so much better. It provides a Typescript item template for Visual Studio. When the compilation process is kicked off it will also create the JavaScript for the Typescript file as well.

Type Script Definition Files

One of the nice things about Typescript is that is will moan at save time (with the Web Essential Visual Studio Extensions installed) if you do not have the appropriate definition files loaded. Here is an example of that : 

Click for larger image 

Luckily help is at hand by way of the awesome set of definition files that are available for all the major JavaScript libraries out there. 

You just need to head over to the DefinitelyTyped web site which has definition files for Typescript of all the major JavaScript libraries out there. Here is an example of what it looks like with the correct definition files loaded, see how the compile errors have gone. Oh all I did was include the correct definition files from the DefinitelyTyped web site and included them in the the project and dragged them onto the Typescript file.

Click for larger image 

There is one last thing that I wanted to mention when using Typescript and RequireJs together, which is a command line argument that MUST be provided to work with RequireJs correctly.

This is the module type which can either be "commonJS" or "amd". "commonJS" would be used if you were doing node development, whilst "amd" would be used when you are doing things like RequireJs. By the way amd stands for "Asynchronous Module Definition".

There is a really nice article available here which has some more information avaiable on the differences between "commonJS" and "amd" modular javascript:

http://addyosmani.com/writing-modular-js/

Luckily installing the eb Essential Visual Studio Extension makes things a lot easier for us, as all we need to do is go set an Option in Visual Studio and the correct command line will be emitted for us.

Anyway enough of all the background stuff, let's see some code shall we.

This is the raw html demo page

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css" />
    <script data-main="scripts/app" type="text/javascript" 

src="scripts/lib/require.js"></script>
</head>
<body>
    <h1 id="header">TypeScript HTML App</h1>

    <div id="content" />
</body>
</html>

Where is can be seen that we use the same stuff as before where we link through to a scripts/app bootstrapper file. This bootstrapping file is shown below. It is largely the same as previous examples but we are of course able to make use of some of Typescripts features such as lambdas ( () => ). You can click the image below to see a larger version of what JavaScript gets created. Like I say it is largely the same as the previous examples:

Click for larger image 

So now that we have a bootstrapper in place, lets look at the main app.js file.

I just wanted to show you the difference between what the Typescript compiler would do depending on the command line option for picking your module type that we discussed earlier, before we talk about the nitty gritty of using RequireJs (although if you just read the code below, you can see it clearly):

Use the AMD Module Command Line

This is what would be compiled (the JavaSript is on the right hand side) with the AMD command line arg turned on. 

Click for larger image 

Don't Use the AMD Module Command Line (CommonJS Style)

This is what would be compiled (the JavaSript is on the right hand side) with the AMD command line arg turned off.

Click for larger image 

See how its quite different. In fact using the "amd" command line argument causes the Typescript compiler to actually use RequireJs syntax.

Anyway to use RequireJs inside of a Typescript file is pretty easy, all you have to do, is make sure the Typescript file knows about the correct RequireJs definition file (which we talked about earlier) and then start using RequireJs.

Here is small example:

require(['jquery'], ($) => {
    var el = $('#content');
    var greeter = new gt.Greeter(el);
    greeter.start();
});

I am using a vriation of the Greeter example as shown on the Typescript web site, where we simple update a DOM element. There is one thing to be aware of since I also wanted to use jQuery to pick out the HtmlElement to pass to the Greeter  you are then forced into using the Typescript type any, as it does not recognize a jquery wrapped HtmlElement as a HtmlElement type.

Anyway here is the code for the Greeter:

Click for larger image 

That's It

Anyway that is all for now, I hope you enjoyed it, and maybe even learnt something along the way.

 

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here