It can be a bit of a headache to work with multiple JavaScript files on one webpage. When working with data visualizations, for example, I have to remember that D3.js needs to load before libraries built on top of D3 (e.g., Plottable.js).

The straightforward way to handle this is to place the code that loads D3.js before the code that loads Plottable.js:

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.12/d3.min.js" charset="utf-8"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/plottable.js/2.0.0/plottable.min.js"></script>

While this will work, it will unnecessarily increase page load times, as the browser waits for D3 to fully load before downloading the Plottable code; Google’s PageSpeed Insights will balk at your “render-blocking JavaScript,” encouraging you to load your JavaScript asynchronously (i.e., in parallel rather than one after the other) instead. Another strike against this straightforward “List out the JavaScript files you want to load in the correct order given their dependencies” approach is that you have to remain cognizant of the interrelationships between your JavaScript files. While this might not be a huge cognitive burden for small projects, it can become a significant issue (and source of bugs) in larger projects with dependencies that are overlapping or nested. It’s much better to have a “set it and forget it” approach to JavaScript dependency management.

Require.js is the JavaScript library that I use on this site to address these sequencing and dependency issues. Require.js downloads JavaScript files asynchronously yet loads them in an order compatible with dependencies; also, it uses a “set it and forget it” format for declaring dependencies that works from project to project. (As with many pieces of software, Require.js can do more than the in-browser JavaScript file management functionality I’m using it for here–notably, it can combine and minify JavaScript files when run on Rhino or Node.)

Let’s say we’re building a website that needs to contain the following JavaScript files (arrows indicate dependency):

Javascript Dependencies

  • Plottable.js, the aforementioned plotting library that is built atop D3.js
  • Dygraphs, a library for visualizing time series data
  • Two custom-built scripts (footnotes.js and map_selector.js) that both depend on Bootstrap.js, a library of jQuery plugins for adding interactivity to websites.

Given this dependency structure, Dygraphs can be loaded whenever; Plottable.js must be loaded after D3.js, and both footnotes.js and map_selector.js must be loaded after Bootstrap.js. (Either can be loaded before the other, however).

There are three steps to loading these files using Require.js:

  1. Loading Require.js: Add one script tag to the webpage
  2. Configuring Require.js: Create a main.js file
  3. Handling Dependencies: Embed dependency information in JavaScript files

Loading Require.js

To load these six files using Require.js, download the Require.js file from the library’s homepage and place it on your server. For our example here, we’ll assume that there is a scripts folder at the same directory level as our webpage that contains Require.js along with our other scripts. After uploading Require.js, we’ll insert the only script tag we’ll need to use near the bottom of the page:

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

The src="../scripts/require.js" attribute simply loads the Require.js file as we’d typically load a JavaScript file. The data-main="../scripts/main" attribute does two main things. First, it points Require.js to the location of a main.js file that tells it what Javascript files to load. Second, it sets the root directory for files referenced within main.js to be the directory where main.js resides, so a reference to “footnotes.js” within main.js is interpreted as a reference to the file footnotes.js within the scripts directory that main.js is in.

One potential gotcha: the .js file extension is left off of the data-main attribute (and, as we’ll see, references within the main.js file) by convention.

main.js is essentially the main Javascript configuration file for your webpage. For our example, main.js looks like this:

require.config({
    paths: {
        d3: 'd3.min',
        plottable: 'plottable.min'
        dygraphs: 'dygraph-combined'
        bootstrap: 'bootstrap.min',
        footnotes: 'footnotes',
        map_selector: 'map_selector',
    }
    shim: {
        'plottable': {
            deps: ['d3']
        }
    }
});

require(["plottable", "dygraphs", "footnotes", "map_selector"]);

The paths property of the object passed to require.config simply creates aliases that you can use to refer to your JavaScript files. Since we assumed that our files are located in the same directory as main.js, we only need to list out their filenames (leaving off the .js extension, per convention).

We’ll discuss the shim property of the object passed to require.config in the next section, since it’s handling the dependencies of Plottable.

The require() function at the end of main.js takes in an array of strings corresponding to the aliases of the JavaScript files that we want to load, omitting files that are simply dependencies for other JavaScript files (in this case, D3 and Bootstrap).

Handling Dependencies

Almost done! require(), which we saw above, is one of the two main workhorse function of Require.js; the other is define(), which is included in JavaScript files that you write. Here’s how it works inside our footnotes.js:

define(['bootstrap'], function(bstrap) {
    // Do magical footnote things referencing the Bootstrap library with bstrap.
})

The define() function is called on the first line of footnotes.js, wrapping the entire contents of the file. The first parameter of define() is an array containing the aliases of the files that the current file depends on; in this case, footnotes.js depends on Bootstrap, so we include an array containing 'bootstrap'. The second parameter is an anonymous function containing all of the code within footnote.js. You pass this function references to use for the libraries included in the array; in this case, we’re referencing the Bootstrap library with bstrap.

To handle dependencies within the two JavaScript files that we’re writing–footnotes.js and map_selector.js–we simply need to wrap their contents in a define() function as above and Require.js will know what to do. For JavaScript files that we didn’t write–in this case, Plottable.js–we’ll use the shim property of require.config to tell Require.js about its dependencies:

    shim: {
        'plottable': {
            deps: ['d3']
        }
    }

The above code that we included in our main.js file simply tells Require.js that Plottable depends on D3. Pretty simple! A couple notes here, though:

First, since we have the Plottable JavaScript file stored on our server, we could have simply edited the file and wrapped its contents inside a call like

define(['d3'], function(d3) {
    // Entire code for Plottable.js here
})

While this would work, it sets us up for trouble if we update Plottable in the future; if we forget to add the define() function back in, we’ll fail to load D3, preventing Plottable from working.

Second, given how easy it is to set the shim property, one might wonder why we shouldn’t just use shim for everything and not bother with define(). The official Require.js answer revolves around how using define() creates JavaScript “modules,” essentially packages of interrelated code scripts. As a Python user, my personal take on why you shouldn’t use shim for everything comes from the Zen of Python: “Explicit is better than implicit.” If your footnotes.js file depends on bootstrap.min.js having run first, there should be something explicitly in the code of footnotes.js that tells you that! Having to dig around for a setting in a main.js file to find the dependency structure makes confusion (and, therefore, bugs) more likely.

That’s it! With the one script tag on our webpage, our main.js file, and our use of shim and the define() function to handle dependencies, Require.js will now efficiently load all of our JavaScript files.

Stray Observations

  • Since Dygraphs does not have any dependencies, all we needed to do was include it in our call to require() in main.js.
  • The define() function is not just part of Require.js; it’s part of the Asynchronous Module Definition API, currently adopted by jQuery, Dojo, and others.
  • Despite Require.js not being a part of WordPress’s official guidance for using JavaScript, it works perfectly well within WordPress posts (including the post you’re currently reading!).