Getting Started Managing Client Side Scripts with Require.js

Back in the 90s when I started my development career, the first language I learned was Javascript. It is a deeply and perfectly flawed language. We created huge DHTML messes with it. And I moved on to server side development and got lost in .NET for a while. It all kind of left me with the following feeling.

With the rise of jQuery and other frameworks like it, javascript has come back front and center for me. As time goes by, I find I am doing more and more javascript daily. In fact, at Cheezburger javascript is about 80% of my day now. jQuery removes some of the browser compatibility burdens we felt way back in the 90s. And over the course of the last few years I have created a few jQuery messes.

Those messes look a little something like this.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Getting Started with Require.js</title>
		<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
		<script type="text/javascript">
			$(function(){
				var cfg = $.parseJSON($('#config').html());
				if(cfg.isProduction)
					$('h1').css('background-color', 'red');
			});
		</script>
	</head>
	<body>
		<h1>Sample Application</h1>

		<p>This is a sample application used in a blog post to demonstrate 
			how to get started with <a target="_blank" href="http://requirejs.org">require.js</a>.</p>

		<script id="config" type="application/json">
			{
				"isProduction": true
			}
		</script>
	</body>
</html>

This is a very simplified example, but it is pretty representative of what I used to do. The server has rendered some client side configuration information into an application/json script tag. In the head of the document we have a script tag to include the minified jquery library from the Google CDN. There is also a script block that uses the jquery ready function to read the configuration information and modify the mark up based on a setting.

One problem presented here is the nature in which browsers load javascript. Script tags are blocking operations. Meaning when the browser hits a script tag the script must be downloaded and fully evaluated before continuing. This is why some developers have adopted the convention of putting script tags and blocks at the end of the document body. This way all the mark up is rendered and then scripts start to load and execute.

Now imagine this sample also used about twenty jquery plugins. All those plugins have to load in a blocking fashion and all of them must be in the right order in the markup to satisfy dependencies. And every member of my team must be aware of those dependencies as well and care and feed them. Additionally every script must load before we can fire off any functionality. Are you beginning to understand why some sites are so damn slow to render?

Finally, with this example there is no concept of modularity. I simply open up a ready function and start pluggin away. Grabbing data directly from its source and poking at the dom directly. Abstractions were not my thing apparently, running with scissors as close to the metal as possible seems more accurate.

When I started at Cheezburger, I was introduced to require.js by Matt Mirande. Matt has become my personal javascript savior. He sat me down and we had a come to black baby jesus talk and I am a better man for it.

The best way I can describe require.js is IoC for javascript. If you are not familiar with IoC, please get thy self to the Castle project and do some learnin. Require.js allows you to think about your javascript in terms of discreet modules of functionality. It manages loading those modules asynchronously and executes them as their dependencies are satisfied.

I think the best way to describe require.js might be to simply demonstrate it by converting my example. To start, simply download the require.js library from the site and put it in the root of your scripts directory. Then add a reference to it. The final markup looks like this.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Getting Started with Require.js</title>
		<script type="text/javascript" src="scripts/require.js"></script>		
		<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
		<script type="text/javascript">
			$(function(){
				var cfg = $.parseJSON($('#config').html());
				if(cfg.isProduction)
					$('h1').css('background-color', 'red');
			});
		</script>
	</head>
	<body>
		<h1>Sample Application</h1>

		<p>This is a sample application used in a blog post to demonstrate 
			how to get started with <a target="_blank" href="http://requirejs.org">require.js</a>.</p>

                <ol id="status">
			<li>loading...</li>
		</ol>

		<script id="config" type="application/json">
			{
				"isProduction": true
			}
		</script>
	</body>
</html>

Note that I added an ordered list to the markup. I will be using it to demonstrate the load order of various things I am about to show you. Next, we need to tell require.js what to do. Create a javascript file right next to require called main.js. this will be the main entry point for our require context. The contents of this file looks like this.

require([], function(){
	 var status = document.getElementById('status');
	 var item = document.createElement('li');
	 item.innerText = "main loaded";
	 status.appendChild(item);
});

We then need to change our reference to the require.js script to use our new main entry point script. Simply add a data-main attribute to the script reference like so. You can drop the .js off the end of your file name and the path is relative to the require.js file.

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

This script calls the require function passing in an array of dependencies and a function to call when the dependencies have been loaded and executed. Right now I don't have any dependencies, so I pass an empty array. The function simply uses some straight DOM manipulation to add a list item to my status list.

All this DOM manipulation is silly, considering I have already established the use of jquery. So I am going to fix that up by making jquery my first dependency. jQuery is special as a dependency because I still want to load it from the CDN and it is not a proper AMD module. No problem require.js can handle that.

require.config({paths: { jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min' }});

require(['jquery'],function($){
	$('<li>main loaded</li>').appendTo('#status');
});

Using the config method of require, I can set the path that jquery should be loaded from. I only need to do this once in the main entry point script. All other modules can simply add a dependency to jquery and not worry about it.

My require statement has changed a little bit. I have explicitly added jquery as a dependency and pass a reference to it in my callback function. At this point I am free to use jquery as I see fit. jQuery plugins will work the same way. If you are loading them from a CDN, set the path and simply add them to the dependency list. if you don't need to interact with the plugin directly, you can drop the reference to the function.

require(['jquery','plugin-with-methods', 'just-needs-to-load'],function($, plugin){
	plugin.dosomethingwith($);
});

Note that by convention libraries that simply need to load should be at the end of the dependency chain to not cause issues with the function call.

We can now safely remove the reference to jquery from our markup and migrate our ready function into the body of our main.js require call.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>Getting Started with Require.js</title>
		<script type="text/javascript" src="scripts/require.js" data-main="scripts/main"></script>
	</head>
	<body>
		<h1>Sample Application</h1>

		<p>This is a sample application used in a blog post to demonstrate 
			how to get started with <a target="_blank" href="http://requirejs.org">require.js</a>.</p>

		<ol id="status">
			<li>loading...</li>
		</ol>

		<script id="config" type="application/json">
			{
				"isProduction": true
			}
		</script>
	</body>
</html>
require.config({paths: { jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min' }});

require(['jquery'],function($){
	$('<li>main loaded</li>').appendTo('#status');

	var cfg = $.parseJSON($('#config').html());
	if(cfg.isProduction)
            $('h1').css('background-color', 'red');
});

We have now successfully set up require.js and modified our use of jquery so that it is treated like any other require module. I can now start breaking out useful functionality into reusable modules. It seems like configuration is a pretty obvious module. Lots of other modules will want to use the configuration object and knowing how to read it out of the markup seems like a SRP violation.

I'll start by creating a mods folder in my scripts folder to hold all my modules. Then create a configuration.js file in it. To define a module you call the define method, pass in your dependencies as an array and a function.

define(['jquery'], function($){
	$('<li>configuration loaded</li>').appendTo('#status');
});

This module simply appends a meesage to our status list that it has been loaded. Let's modify our main.js to depend on this module.

require(['jquery', 'mods/configuration'],function($, configuration){
	$('<li>main loaded</li>').appendTo('#status');

	var cfg = $.parseJSON($('#config').html());
				if(cfg.isProduction)
					$('h1').css('background-color', 'red');
});

All I need to do is add the relative path of the configuration module to the dependencies list and add a reference to it in the callback function signature. Executing this code right now yields the following output in our status list.

  1. Loading...
  2. configuration loaded
  3. main loaded

Note that the configuration module is loaded and executed first. Then the main entry point is executed. This chain of dependencies is figured out by require.js. Now let's flesh out our configuration module.

define(['jquery'], function($){

	var module = {};

	module.getConfig = function(){
		return $.parseJSON($('#config').html());
	};

	$('<li>configuration loaded</li>').appendTo('#status');
	return module;
});

And finally, modify our main.js to consume the new module.

require(['jquery', 'mods/configuration'], function($, cfg) {
	$('<li>main loaded</li>').appendTo('#status');

	if(cfg.getConfig().isProduction)
		$('h1').css('background-color', 'red');
});

Now our consuming code has no knowledge of how the configuration is retrieved, it just uses the values. We can feel free to refactor the configuration module without effecting consumers at all. Lets do that and add some caching so we are not reading the DOM every time we get the configuration.

define(['jquery'], function($){

	var config, module = {};

	module.getConfig = function(){
		return config || (config = $.parseJSON($('#config').html()) || {});
	};

	$('<li>configuration loaded</li>').appendTo('#status');
	return module;
});

Bam, application keeps on humming. Nice. Now admittedly this example is pretty contrived. But I hope the point was delivered and you could follow all the moving parts. If you are interested in trying it out the project is up on github. Why not try implementing a module to handle our little status logging implementation?

Update: Here are some comments I got from Mr. Mirande on this post. I thought my readers might be interested in hearing the critique of this article from my javascript mentor.

First off, wow, totally honored / humbled by the shout-out dude! So awesome - Thanks! :-) Second, great post sir! Was a fun and fast read which managed to pack almost all the key points into a nice, digestible nugget. Couple things I noticed... and in general these point to main flaw in the whole AMD thing: things can get a bit fiddly / WTF-y once you get past the initial setup... and, well, initial setup itself is kind of a pain :-)

1: "jQuery... is not a proper AMD module" - well, it kind of is. It's just making use of a feature of AMD that is usually best avoided - named modules. For almost every use-case, you want to go with "anonymous" modules as they are easier to move around and require less boilerplate. We do actually use named modules during testing (via our testing.js thinger (testing.req.using() ) this is how we stub out dependencies at the module level.

2: jQuery plugins - This is trickier... basically only AMD modules (shim'ed or otherwise) can be executed in a specific order. Normal, non-AMD, scripts will be load and executed immediately. So, in your example where you load jQuery as a module and a jQuery plugin as a script, there's a chance the plugin will load before jQuery itself and throw an error. >_< In require 2.x, they added the concept of a "shim" to better accommodate scenarios where devs are working with both AMD and non-AMD resources. Again, shit gets fiddly :-( so I've always just wrapped plugins such that they operate either as AMD modules or standard browser globals. Neither approach is really friendly to folks just getting started unfortunately.

3: Module return values - I like how you describe the ordering of dependencies in the define() / require() callback functions but you might want to be more specific in describing the module mechanic (e.g. explicitly returning something vs. not). Personally, I found this to be the key revelation when working with AMD - each module offers it's own "sandbox" of sorts - a new scope where you don't have to worry about name collisions, can specify public / private things, etc.

Anyway, I don't think these are major omissions... it's really tricky to find the right level of detail when explaining AMD to newbs and I think you've pretty much nailed it... but I figured I'd point them out in case they are helpful.

Follow me on Mastodon!