Lazy Loading RequireJS Modules When They Are First Requested

I've been loving RequireJS as a framework that helps me think about my JavaScript applications in a more modular fashion. However, in my research and development, I've always been loading all of my RequireJS modules upfront, at the start of the application. As I've begun to move some RequireJS functionality into production, I've found myself not wanting to eagerly load modules that hardly ever get used. Rather, I'd like to lazy load some modules if, and only if they ever get requested by the user.

To explore the lazy loading of RequireJS modules, I set up a simple test page with some content and a "help" link at the bottom. The help link at the bottom makes use of an FAQ module; however, since most people will never use this link, I don't want to load the FAQ module until it is absolutely necessary (ie. until the user clicks the link).

index.htm - Our Demo Page

<!doctype html>

<html>

<head>

<meta charset="utf-8" />

<title>Lazy Loading RequireJS Modules</title>

<link rel="stylesheet" type="text/css" href="./css/demo.css"></link>

<!-- Load RequireJS and define the bootstrap file. -->

<script

type="text/javascript"

src="./js/lib/require/require.js"

data-main="./js/main.js">

</script>

</head>

<body>

<h1>

Lazy Loading RequireJS Modules

</h1>

<ul class="m-nav">

<li>

<a href="#">Home</a>

</li>

<li>

<a href="#">About</a>

</li>

<li>

<a href="#">Contact</a>

</li>

</ul>

<p>

Here is some content for the demo - this is just page filler.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam

sit amet volutpat sapien. Lorem ipsum dolor sit amet,

consectetur adipiscing elit. Etiam fringilla consectetur orci.

Cras enim lectus, mollis ac luctus sit amet, dignissim sed

ante. Cras non erat massa, sit amet elementum sapien. Sed

ac sapien sem. Quisque in mauris mi. Nulla pharetra accumsan

erat. Ut sit amet eros dui. Aenean eget eros sit amet ante

bibendum convallis at ut dui. Lorem ipsum dolor sit amet,

consectetur adipiscing elit. Donec tristique nisl nec nibh

faucibus tincidunt et in leo. Nunc orci est, dictum vel

adipiscing pharetra, hendrerit et sem. Duis risus erat,

hendrerit quis facilisis ac, feugiat eget urna.

Pellentesque habitant morbi tristique senectus et netus

et malesuada fames ac turpis egestas.

</p>

<!-- BEGIN: Lazily Loaded Module. -->

<p class="m-help">

Need help? <a href="#">Check out our FAQs</a>.

</p>

<!-- END: Lazily Loaded Module. -->

</body>

</html>

If you look at the index page, you'll see that there is a "check out our FAQs" link at the bottom. When this link is clicked, I need to load the FAQ module and then open it.

RequireJS provides the require() function which can be used to load modules on-demand. However, in order to make sure that the intermediary / loading state of the module doesn't cause unexpected behaviors (or duplicate behaviors), I have to add some logic that "debounces" clicks (in a loose sense) while RequireJS is asynchronously loading the requested module.

For this demo, I have put the lazy loading logic in the bootstrap file.

Main.js - Our Application Bootstrap

// Set up the paths for the application.

requirejs.config({

paths: {

"domReady": "lib/require/domReady",

"jquery": "lib/jquery/jquery-1.7.2.min",

"templates": "templates",

"text": "lib/require/text",

"views": "views"

}

});

// Run the scripts when the DOM-READY event has fired.

require(

[

"jquery",

"domReady!"

],

function( $ ){

// Since the Help / FAQ module is probably going to be rarely

// used by the user, I don't want to bother loading it as

// part of the initial page load. As such, I'll lazy-load it

// when the "launch" link is clicked.

(function(){

// Our FAQ module will start out as null until loaded.

// And, it's not loaded until it's first needed.

var faq = null;

var body = $( "body" );

var launchFaq = $( "p.m-help a" );

// I load the FAQ module the first time it is needed.

var handleClick = function( event ){

event.preventDefault();

// Check to see if the FAQ module is currently being

// lazily loaded.

if (faq === "loading"){

// Ignore this click - when the module finallly

// loads, it will open the module.

return;

}

// Check to see if the module has been loaded.

if (faq !== null){

// Open the FAQ module for a subsequent time.

faq.open( body );

// The module is unloaded and unrequested. Let's load

// it for the first time and then open it.

} else {

// Set an intermediary value to the faq module so

// that subsequent requests don't try to launch

// the module more than once.

faq = "loading";

// Load the FAQ module.

require(

[ "views/faq" ],

function( FAQ ){

// Create and cache an instance of the

// FAQ module.

faq = new FAQ();

// Open the FAQ module for the FIRST time.

faq.open( body );

}

);

}

};

// Bind the click-handler for the help link.

launchFaq.click( handleClick );

})();

}

);

Here, you can see that I have a click handler for the "check out our FAQs" link. In order to lazy load the FAQ module, this click handler needs to be aware of the three states of a lazy loaded module:

Unloaded

Loading

Loaded

The first time that this link is clicked, I am using RequireJS and the require() function to load the FAQ module. This action sets the module reference to an intermediary value of "loading". This is necessary in order to debounce subsequent clicks that may occur during the loading phase. Once the FAQ module is loaded, however, any subsequent clicks will cause the initialized module to be opened.

I don't know if this is overkill. They say that premature optimization is the root of all evil; but is lazy loading rarely-used modules premature? It's definitely easier to front-load all your modules, no doubt; but, the amount of code required to lazy load the modules doesn't seem to be that overwhelming.

Anyway, this is my first pass at this sort of thing. I think I could probably factor out some of this logic and make the code smaller and more straightforward.

Also, if you are interested in the FAQ module I used in this demo, here's the class definition:

Reader Comments

1. Add another variable to keep track of the status of the FAQ module, rather than overloading the faq variable with that responsibility. If you have another variable like faqModuleStatus and values "notloaded", "loading", "loaded" the code should be clearer when you come back to it six months from now.

2. Bear in mind this makes loading a FAQ (potentially) asynchronous. If you want to do anything after loading the FAQ you'll need to add a callback or return a promise. Also, I learned something interesting yesterday: it's better to be always asynchronous than sometimes asynchronous[1]. So add a setTimeout(callback, 0) to make it async in the case where the FAQ module is already loaded.

With these two bases covered, you're much less likely to lose hair trying to figure out why faq doesn't always reference the object you think it does.

Cool, this is a good idea for what I'm bulding. I'm defining widgets as an html chunks with id's on the pages and once the main.js gets loaded it looks for those html instances in the dom so it can construct them, so seems like a good idea to lazy load depending if they're in the page or not.

I'm not sure which version of RequireJs you were using, but in my version, calling require(...) twice on the same module will only load the module once, even if subsequent calls occur before the module has loaded.