RequireJS is one of my favorite libraries because it brings on of my favorite programming concepts to a language (JavaScript) that needs it more than any other: Dependency Injection.
For the uninitiated or the unfamiliar, Dependency Injection (DI) is a means by which developers can specify dependencies for a class or module and have the runtime fulffill those dependencies by means of a mapping, usually defined in either Xml or some kind of Fluent API; my personal preference for DI in .NET is through Ninject. Using DI makes unit testing much easier as it opens up the ability to “mock” components so that they can be configured for the given test being run.
So what does DI have to do with JavaScript and why should you care? Consider how much JavaScript is used in modern web applications, especially when you start bringing in third party libraries like momentjs, KnockoutJS, jQuery, etc. What I have seen happen is you end up with these long lists of JavaScript dependencies which results in additional download time when the user first arrives at your page. Yes, you can use bundling and minification but you are still asking the browser to download JavaScript code that might not even be needed. This is where RequireJS comes in.
RequireJS enables you to organize code into modules which list their dependencies. When you ask for one of these modules RequireJS will check if the library has been downloaded and inject it into the module (if it has already been downloaded, it will not make the fetch). What this means is user’s download libraries as they need them, instead of “in case they need them”. This also produces code that is better organized, more modular, and more testable.
To stat this process, you need to get RequireJS. It can be downloaded here: http://requirejs.org/docs/download.html. However, I would encourage you to read the next section before adding it to the page.
Configure RequireJS
Before RequireJS can begin to manage your JavaScript it has to know what libraries you are using and where to find them (for downloading). This is your configuration file. For this example, we are going to call it main.js and its location will be /Scripts/app/main.js. This path is very important as you will see next.
Define your tag as normal and point it at RequireJS (either locally or remotely, though I recommend locally). Next add a new attribute data-main and point it at your configuration file being sure to NOT include the .js extension. This path needs to be relative to your HTML file. Here is an example:
My structure:
- scripts
- app
- main.js
- require.js
- app
- index.html
My tag
<script src="scripts/require.js" data-main="scripts/app/main"></script>
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
You will find this is very commonly throughout when using RequireJS, the dropping of the .js extension, its part of RequireJS origin’s from the CommonJS module in Node.
So what this code will do is load up the main.js file you created earlier. In effect this becomes your bootstrap (initial) file for your application. That being the case, it makes sense that we would want to provide our configurations here. So let’s add a few: jQuery, underscore, bootstrap, jQueryUI, and momentJS. The first thing we need to do is define their paths.
requirejs.config({ paths: { 'jquery': "http://code.jquery.com/jquery-1.10.1.min", 'jquery.ui': "http://code.jquery.com/ui/1.10.4/jquery-ui", 'underscore': "../underscore", 'bootstrap': "../bootstrap/bootstrap.min", 'moment': "http://momentjs.com/downloads/moment.min" } });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Notice the alias’ being used, these are VERY important as they are used during the injection process to resolve what is needed. In this case we define 5 alias values: jquery, jquery.ui, underscore, bootstrap, and moment. Let’s update the directory structure presented earlier:
- scripts
- app
- main.js
- require.js
- bootstrap.min.js
- underscore.js
- app
- index.html
Path’s defined in the configuration are RELATIVE TO THE CONFIGURATION module, not the HTML page (which makes sense as the location of the HTML page could be different). Also, no .js extension are used at all. This code instructs RequireJS where to find the files, but we still have to help it understand dependencies (ie, knowing that if you ask for Bootstrap you need jQuery as well), in RequireJS these are called shims. They are defined as a second argument to config, here is our shim configuration:
requirejs.config({ paths: { 'jquery': "http://code.jquery.com/jquery-1.10.1.min", 'jquery.ui': "http://code.jquery.com/ui/1.10.4/jquery-ui", 'underscore': "../underscore", 'bootstrap': "../bootstrap/bootstrap.min", 'moment': "http://momentjs.com/downloads/moment.min" }, shim: { 'jquery': { exports: 'jQuery' }, 'jquery.ui': { deps: ['jquery'] ", 'underscore': { exports: '_' }, 'bootstrap': { deps: ['jquery'] }, 'moment': { exports: ['moment'] } } });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
The alias come into play here as we define what that library needs in order to function properly. Keep in mind, that if a module is already defined for RequireJS (that is it uses the define syntax) you do not need to define its shim, RequireJS will simply read it as normal. In fact, when you use RequireJS that is what create to hold your own code. However, in the case of the libraries we are using NONE are defined using the appropriate syntax, so we use a shim configuration for each.
The first line indicates that the alias jquery has no dependencies but exports an object called jQuery (this is the central function which $ aliases). This tells RequireJS that when jquery is listed as a dependency to pass in a reference to jQuery to the module.
Both jquery.ui and bootstrap depend on jquery being present, but they both extend jQuery with additional function calls, so for both we need only list the dependency, but they do not export anything we do not already have.
The final two libraries, moment and underscore are defined similar to jquery. They are simply not written as RequireJS modules, so we need to instruct RequireJS what we expect it to pass to our module when either is listed as a dependency of the module.
In all honesty, this is the extent of the configuration, you are now ready for the hard part: using RequireJS. This is the hard part because this is where many developers, myself included, struggle, because you have to learn how you will organize your code to take advantage of RequireJS. This was easy in object oriented programming where the concept of DI makes complete sense, but its harder in a more imperative language like JavaScript. I will attempt to explain what I have learned using RequireJS.
Your Main is your Ready
Following the configuration file you can define your first RequireJS module, pretend this is the ready function we have all become so familiar with via jQuery. Here is my module:
require(['jquery', 'moment'], function($, moment) { $("#btn-show-time").click(function() { $("#time-output").text(moment().format('MMMM Do YYYY, h:mm:ss a')); }); });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
require operates asynchronously, so the callback occurs when all dependencies have been fulfilled. Remember, RequireJS tracks what dependencies have been fulfilled and will not download those files again. This means if you ask for the same dependency more than once, it will not download it again, but simply reuse it.
In some cases you may need a library which doesnt export anything but rather extends an existing library, bootstrap is such an example. To leverage this, there are a couple of options that it is useful to be aware of.
Based on our current design, we have indicated that bootstrap will depend on the jquery dependency, but it will not export anything. If nothing is exported that it cannot be injected (unless it was previously written as a RequireJS module). In this case, we need only add the dependency as an unmapped parameter.
require(['jquery', 'moment', 'bootstrap'], function($, moment) { $("#btn-show-time").click(function() { $("#time-output").text(moment().format('MMMM Do YYYY, h:mm:ss a')); }); });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
You can do this with jQuery plug-ins as well, the JS file will be included and will simply add its functions to the jQuery object, just as Bootstrap did above. Obviously, unmapped parameters must come at the end of the dependency chain.
The other option is not indicate that these dependencies export a common object, below is an updated shim configuration illustrating the changes we would make (notice I am not just including Bootstrap in this change).
shim: { 'jquery': { exports: 'jQuery' }, 'jquery.ui': { deps: ['jquery'], exports: 'jQuery' }, 'underscore': { exports: '_' }, 'moment': { exports: 'moment' }, 'bootstrap': { deps: ['jquery'], exports: 'jQuery' } }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
This enables us to do the following when defining out module
require(['bootstrap', 'moment'], function(bs, moment) { bs("#btn-show-time").click(function() { bs("#time-output").text(moment().format('MMMM Do YYYY, h:mm:ss a')); }); });
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
Between the two approaches, I like the first approach better. It makes more sense and is more explicit about what is being included. There will definitely be cases where more than one plug-in is desired for usage, you would be redefining jQuery all over the place. I prefer one definition of jQuery and then being able to bring in, selectively, the extensions the module needs added on to support its function.
Modular JavaScript Design
Using a module based approach is quickly becoming a common practice for JavaScript developers. Using modules enables developers to take advantage of many of the features OO developers take for granted, such as member hiding and using namespaces to mitigate conflict. Unfortunately, the topic of Module Based JavaScript design is beyond the scope of what I can talk about in this article, but needless to say RequireJS can and does promote this style of coding.
Further, by using injection, you can understand how you can write unit tests around these modules. For more information on RequireJS I recommend reading the project page: RequireJS, the author goes to great length to explain why RequireJS makes sense for modern web application development.