Lazy Loading JavaScript With RequireJS

As sites are filled with more and more JavaScript, images, and other external assets, web pages have become more and more bloated and performance has started to become an issue. We’ve started to reduce the number of requests by concatenating our JavaScript and CSS files and using image sprites. We’ve shrunk file sizes by minifying and obfuscating our code and optimizing our images. All these measures are good, but they may not be enough. If you have a large JavaScript application, you could have a ton of JavaScript being loaded in that single concatenated file, and a lot of the code may be going to waste because it isn’t used. Let’s try to “lazy load” some of that code using RequireJS.

This article assumes you have some knowledge of RequireJS. If you don’t, then I suggest watching Part 5 of my Backbone Application Walkthrough where I discuss a bit about RequireJS and convert an app to use it. For a more thorough introduction, you can read Modular JavaScript with RequireJS.

What is Lazy Loading?

I’m sure that a great many of you coming to this article already have an understanding of lazy loading, and if you’re one of those finely educated ladies or gentleman, then feel free to skip ahead to the next section, which I’m sure many already did immediately after reading the heading. For those who haven’t been formally introduced, though, we’ll fill in this void for you.

Normally, all the JavaScript that pertains to a certain web page or application is loaded immediately during page load. Of course, not every user will interact with everything on your web page, so, much of the code will just be a waste of the user’s bandwidth, which can be particularly painful for people who have limited amounts of data usage, such as many mobile and satellite internet service plans. It also causes people with slower internet connections (and even people with high speed connections) to wait for the code to load, even if it won’t be used.

Concatenation and minification work to reduce the pain caused by wasted code, but it can’t eliminate it, and for the most part it’s impossible to completely eliminate it all. There will almost always be some code that isn’t used by a user, but lazy loading can help a lot in alleviating the amount of wasted code. Lazy loading is loading code only once the user needs it. So if you have a button on your page that will show a completely different screen to the user once it is pressed, then there’s no point in loading the code for that screen right away. You can, instead, load it once that button is pressed. This may cause a slight delay, but it’s nothing the user can’t handle, especially because it’ll only happen the first time because after that, the code will already be loaded, and if you have caching enabled, it may be cached for the next visit. The whole point is to not load scripts until they are necessary. This may sound difficult, but as you’ll see in the next section, it’s actually quite simple.

Making RequireJS Lazy

RequireJS is the secret sauce that makes lazy loading simple. If you’re not already using it for dependency management, go read a tutorial and start using it. Or you can use some other dependency management library, but I highly recommend RequireJS and this tutorial will only be talking about RequireJS.

The key to lazy loading is reacting to user input. So, like I said in the previous section, if a user clicks a button that loads an entirely new screen, the code for that screen should only be loaded after that button is pressed. So, we simply need to use RequireJS to require some code inside of the button’s event handler.

1
2
3
4
5
6
7
8
$('#somebutton').on('click', function() {
require(
['every', 'javascript', 'dependency', 'for', 'other', 'screen'],
function(ev, js, dep, fr, othr, scrn){
// Create the screen with your dependencies
}
);
});

The biggest difference between “normal” usage of RequireJS and using it for lazy loading is that you use require within a function that is called at a later time (such as when a button is clicked). That’s seriously the only difference.

Of course, good developers don’t go filling their event handlers with require statements. Good developers have organized code and separate concerns into different objects and functions. So let’s take a look at how we might accomplish all of this with a standard Backbone application. In the example below you’ll see that I’m keeping a lot of the work inside the router. This is actually pretty widely accepted, but I prefer to keep that logic in a separate controller in my own applications. I’m using the router here to simplify the code a bit and because it’s pretty well known how the router normally works for Backbone applications. Also, we’re going to

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// View logic
AView = Backbone.View.extend({
events: {
'click button': 'edit'
},
...
edit: function() {
var id = this.getId(); // Just some type of calculation
App.router.navigate('thing/' + id, {trigger: true});
}
});

Router = Backbone.Router.extend({
routes: {
'thing/:id': 'edit'
},
edit: function() {
require(
['every', 'javascript', 'dependency', 'for', 'edit', 'screen'],
function(ev, js, dep, fr, edit, scrn){
// Create the screen with your dependencies
}
);
}
});

So, basically all I did was put all the logic for lazy loading and setting things up into the router, which – unless you’re using a controller – is where it should be.

The Lazy Loader

In my current project, I’ve actually created an abstraction from this, so that the controller doesn’t need to mess with RequireJS directly. It’s called LazyLoader.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var LazyLoader = function(type) {
this.type = type;
};

_.extend(LazyLoader.prototype, {
get: function() {
var fileNames = Array.prototype.slice.call(arguments);
var dfd = $.Deferred();
var path = this.type + "/";

fileNames = _.map(fileNames, function(fileName){
return path + fileName;
});

require(fileNames, function() {
dfd.resolve.apply(dfd, arguments);
});

return dfd.promise();
}
});

The constructor takes a single parameter, which is then used as the directory for resources you’re trying to load. You then use its get method to retrieve any number of dependencies. The function returns a promise, which you can then use then or done on to actually complete what you need to do. For example:

1
2
3
4
5
6
7
8
9
10
11
var loader = new LazyLoader('views'); // Now when I request a file, it'll be from views/*

// Load one resource and work with it
loader.get('some-module').then( function(SomeModule) {
// set up SomeModule;
});

// Or you can load multiple, just like RequireJS
loader.get('some-module', 'another-module', 'one-more-module').then( function(Mod1, Mod2, Mod3) {
// Use the modules
});

The reason I did this is two-fold. First of all, if I decide to use a library other than RequireJS to load the modules in the future, I simply have to update the LazyLoader instead of searching for everywhere I used lazy loading. The other reason I wanted it is because then I can make a simple API for getting a resource within my application. I simply attach different loaders to certain properties of my applications object. In an application where lazy loading isn’t used, many people will attach all of their view classes to App.Views and things like that. In an application where we can’t know that the view was loaded we need a way to make sure their loaded, but I still want it to be found on App.Views. So I use the LazyLoader like this:

1
2
3
4
5
6
App.Views = new LazyLoader('views');
App.Models = new LazyLoader('models');
...

// Now we want to use a view
App.Views.get('some-view').then(...);

It just seems to make sense to grab a view class using App.Views.get, doesn’t it? That’s why I made the lazy loader instead of just sticking with RequireJS code. It’s clear what you’re doing.

Of course, this presents a problem when you need to load resources of different types, e.g. a view and a model. But the promise API has ways of dealing with this. I like to take care of it like this:

1
2
3
4
5
6
var getView = App.Views.get('some-view');
var getModel = App.Models.get('some-model');

$.when(getView, getModel).then( function(SomeView, SomeModel) {
// Use SomeView and SomeModel
});

If you understand how to use promises, then this will all make sense. If you don’t understand how to use promises, then I suggest you read up on them. There are some weird gotchas with the way that the parameters are passed in to the then function above. I’ll give you a couple examples to show you what I mean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Request one file per call to 'get'
var getView = App.Views.get('some-view');
var getModel = App.Models.get('some-model');

$.when(getView, getModel).then( function(param1, param2) {
// param1 = the module from 'some-view'
// param2 = the module from 'some-model'
});

// Request multiple files from one 'get'
var getView = App.Views.get('some-view', 'other-view');

$.when(getView).then( function(param1, param2) {
// param1 = the module from 'some-view'
// param2 = the module from 'other-view'
});

// Request multiple files with multiple calls to 'get'. This is where it gets interesting
var getView = App.Views.get('some-view', 'other-view');
var getModel = App.Models.get('some-model');

$.when(getView, getModel).then( function(param1, param2) {
// param1 = array -> [module from 'some-view', module from 'other-view']
// param2 = the module from 'some-model'
});

// Another multiple x multiple
var getView = App.Views.get('some-view');
var getModel = App.Models.get('some-model', 'other-model');

$.when(getView, getModel).then( function(param1, param2) {
// param1 = the module from 'some-view'
// param2 = array -> [module from 'some-model', module from 'other-model']
});

// Another multiple x multiple
var getView = App.Views.get('some-view', 'other-view');
var getModel = App.Models.get('some-model', 'other-model');

$.when(getView, getModel).then( function(param1, param2) {
// param1 = array -> [module from 'some-view', module from 'other-view']
// param2 = array -> [module from 'some-model', module from 'other-model']
});

I hope you understand how that’s working because I really don’t want to put it into words. Anyway, if you don’t want to deal with arrays of modules being passed in, then you can change that last example to something like this:

1
2
3
4
5
6
7
8
9
var getSomeView = App.Views.get('some-view');
var getOtherView = App.Views.get('other-view');
var getSomeModel = App.Models.get('some-model');
var getOtherModel = App.Models.get('other-model');

$.when(getSomeView, getOtherView, getSomeModel, getOtherModel).then(
function(SomeView, OtherView, SomeModel, OtherModel) {
// There, now each of your modules have their own parameter again.
});

Conclusion

I guess this ended up being more of an introduction to using my utility class for lazy loading, than an introduction to using RequireJS for lazy loading, but I still got the point across. Try it, see how you like it, and see how much faster it is to load your application! It’s up to you to decide whether a 5-second initial download is worth converting to a 1-second initial download with other small downloads littered here and there, but in the end, your users are the ones who will be the ones who will decide whether they’ll use it depending on those download times. I hope that I’ve given you enough knowledge how to do it so you can go make your apps better. God Bless and happy coding!

Author: Joe Zimmerman

Author: Joe Zimmerman Joe Zimmerman has been doing web development ever since he found an HTML book on his dad's shelf when he was 12. Since then, JavaScript has grown in popularity and he has become passionate about it. He also loves to teach others though his blog and other popular blogs. When he's not writing code, he's spending time with his wife and children and leading them in God's Word.