Lazy Loading JavaScript with RequireJS

Lazy Loading with RequireJSAs 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.

$('#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

// 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.

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:

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:

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:

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:

// 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:

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!

About the Author

Author: Joe Zim

Joe Zim

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.


  • http://www.facebook.com/people/Andrea-Baccolini/755699316 Andrea Baccolini

    A Demo pls!

    • http://www.joezimjs.com Joe Zimmerman

      Is there something that you think a demo would help you understand better? I felt like everything was explained well enough that a demo wouldn’t be necessary.

      • http://www.facebook.com/pixel67 Tony Brown

        demo’s always drive home the lesson, at least they do for me, when one can see it working visually it really helps to make the idea `stick`

      • http://www.facebook.com/pixel67 Tony Brown

        a `real world` example not something stupid :D

  • JL WebDev

    Awesome tut! I really like the way you code. I’m learning MVC and I’d like to implement it in my current project. What do you recommend? Just look at these ressources: http://www.joezimjs.com/recommended-resources/ ? :)

    • http://www.joezimjs.com Joe Zimmerman

      If you already have a decent grasp on JavaScript itself, I’d say jump right into Developing Backbone.js Applications (listed on the resources page). If you’re interested in throwing a Node.js back end onto your apps, then make sure to read Building Node Applications with MongoDB and Backbone (listed on the resources page) too. Otherwise, yea, I’m sure there’s something in each of the books on that page that you can learn from.

  • Santosh A.

    Awesome article JoeZ

  • http://twitter.com/LesSzklanny Les Szklanny

    Do you need to rename require to e.g. req? Otherwise, the optimization tool will pick up the dependencies and include them in the built file(?)

    • http://justlaputa.github.com/ Han Xiao

      I have the same concern, how will almond.js handle this lazyloader when compressing?

      • http://www.joezimjs.com Joe Zimmerman

        Good question guys. I haven’t looked into that yet, but I’ll get back to you as soon I take a look. In the mean time, there is nothing stopping you from trying it yourselves. :)

        I believe that if you used something like my utility LazyLoader class, it would still lazy load the files. If you use almond.js instead of require.js in your production build, then I really have no clue because I haven’t looked into almost.js enough to know whether or not it even has the ability to load files. Like I said, I’ll look into it.

      • http://www.joezimjs.com Joe Zimmerman

        almond.js will fail with an error because it doesn’t have a script loader. You can always switch back to using just require.js, or you could use a different library for lazy loading (like yepnope.js).

        @twitter-315953555:disqus, the optimizer only looks for “top level” calls to require/define and optimizes those. The dependencies that you want to lazy load should not be added to the optimized file (unless something else that is already loaded uses them as a dependency, obviously).

  • http://twitter.com/tysonnero Tyson Nero

    Great article. I always wondering if and how this would work, and now I know.

  • colynb

    Lazy loading is great but I would avoid using it on user generated events like the click event, at least not without thinking about it very carefully. There’s an expectation of responsiveness that if disrupted, can really degrade the user experience. This is especially noticeable on devices with slow connection speeds like mobile phones.

    • http://www.joezimjs.com Joe Zimmerman

      This is very true, but I’ve also seen evidence that if something seems like it should take a while to process and it feels instantaneous, users sometimes wonder if the processing even took place. In either case, the main thing is to provide feedback to the user. If something takes too long, show them something indicating that it’s taking a little while to load. When something finishes, inform the user that it finished. The great thing about lazy loading is that it will only take time the first time you do it.

  • colynb

    love your use of promises though!