Andrew Welch · Insights · #loadjs #frontend #Javascript

Published , updated · 5 min read ·


For more tools, technologies, and techniques, check out the devMode.fm podcast!

LoadJS as a Lightweight JavaScript Loader

Here’s how to use Load­JS as a light­weight JavaScript loader & depen­den­cy man­ag­er instead of using

Some­thing fun­da­men­tal has changed with this web­site. Assum­ing I’ve done my job prop­er­ly, the cool part is you shouldn’t notice at all.

What has changed is that I’m no longer load­ing the JavaScripts used on this web­site via Sys­temJS, as dis­cussed in the Using Sys­temJS as a JavaScript Loader arti­cle. All of the same rea­sons for eschew­ing <script> tags still apply, of course, I’m sim­ply using a dif­fer­ent mech­a­nism to reap the same benefits.

So why the switch? Sys­temJS is a fan­tas­tic piece of work, and if you are doing heavy fron­tend JavaScript devel­op­ment with Vue, React, or what have you, the abil­i­ty to load mod­ules in a vari­ety of for­mats and dynam­i­cal­ly tran­spile them is pret­ty amazing.

However, I don’t need any of this for my simple website. It’s a bit like owning a Ferrari in Alaska. It’s awesome and all, but you don’t use all it offers very often.

But we still want the abil­i­ty to load our JavaScripts asyn­chro­nous­ly, and we still want some mod­icum of depen­den­cy man­age­ment, so that our JavaScripts all load in a deter­min­is­tic order.

Load­JS is a handy solu­tion for sim­ple fron­tend JavaScript load­ing require­ments. It loads every­thing asyn­chro­nous­ly, it pro­vides the group­ing togeth­er of JavaScripts in bun­dles”, and it pro­vides call­backs when these bun­dles are loaded, just like Sys­temJS and Require­JS.

Load­JS doesn’t pro­vide all of the bells and whis­tles of Sys­temJS, but I don’t need them for this project. And it’s also 0.7K vs. Sys­temJS weigh­ing in at 70K (yes, Load­JS is just 1/100th of the size).

So what we do lose? We lose the abil­i­ty to do any dynam­ic tran­spiling of our JavaScripts. And it only works with mod­ern” browsers (defined as IE 9+). For my pur­pos­es, both of these require­ments are per­fect­ly accept­able. If you require dynam­ic tran­spiling or load­ing of mod­ules in a vari­ety of for­mats, use Sys­temJS. If you require sup­port­ing much old­er browsers, use Require­JS.

But please use some­thing for async load­ing and depen­den­cy man­age­ment of your JavaScripts! All of this will become a thing of the past when­ev­er the Mod­ule Loader Spec is final­ized and imple­ment­ed. But we live in the present!

Link A Lean, Mean, Loading Machine

What I love about Load­JS is that it’s lean and mean. As men­tioned, it weighs in at a sprite­ly 0.7K before being gzip’d.

This allows us to just inline the Load­JS JavaScript in our <head> using the Twig source directive:

{# -- ServiceWorker & loadjs -- #}
    <script>
        {{ source('_inlinejs/register-service-worker.min.js') }}
        {{ source('_inlinejs/loadjs.min.js') }}
    </script>

The source direc­tive works just like Twig’s include, but it doesn’t parse the file as a tem­plate, it just includes the raw con­tent of the file. The only require­ment is that the file exists some­where in your templates direc­to­ry tree.

The register-service-worker.min.js is dis­cussed in the Ser­vice­Work­ers and Offline Brows­ing article.

We include Load­JS ear­ly on in our <head> so that it’s avail­able every­where, as per the rec­om­mend­ed usage:

The recommended way to use LoadJS is to include the minified source code of loadjs.js in your <html> (possibly in the <head> tag) and then use the loadjs global to manage JavaScript dependencies after pageload.

Alright, so now that it’s avail­able, how do we use it? It’s pret­ty sim­ple, we just define a bun­dle” by giv­ing it a list of URLs and a name to assign to the bun­dle. For instance, in my main _layout.twig I define a bun­dle for Vue, some­thing that’s used glob­al­ly on the site:

<script>
    // define a dependency bundle
    loadjs(
        [
            '{{ baseUrl }}js/vue.min.js'
        ],
        'vue'
    );
    loadjs.ready('vue', {
        success: function() {
            // Vue is now loaded and ready to use
        }
    });
</script>

Since we use Vue on all of our pages, we may as well load it in our _layout.twig so it gets down­loaded and cached ear­ly. This is a very sim­ple bun­dle, which has only one JavaScript resource, but you can put as many as you like into the array you pass to loadjs(). The bun­dle name you pass in (vue in this case) is just how you refer to it lat­er on when you want to load it.

The loadjs.ready() func­tion asyn­chro­nous­ly loads in all of the JavaScripts in our vue bun­dle by adding them to the document.head as <script src="" async> tags, which in turn caus­es the web brows­er to load them.

When all of the JavaScripts in a bun­dle are loaded in, it calls the success func­tion, and we now know it’s safe to use Vue. So any code that uses Vue should be inside of this success function.

So that’s pret­ty easy, but what about on our Blog detail pages, where we need a few addi­tion­al resources? No prob­lem, we do it the same way:

<script>
    loadjs(
        [
            '{{ baseUrl }}js/prism.min.js',
            '{{ baseUrl }}js/lazysizes.min.js'
        ],
        'blog-entry'
    );
    loadjs.ready('blog-entry', {
        success: function() {
            Prism.highlightAll();
        }
    });
</script>

In this case, all we care about is that lazy­sizes & prism are loaded, and once they are, we call Prism.highlightAll(); to tell prism to do it’s thing high­light­ing our code samples.

For our blog archives that appear on the bot­tom of each blog entry page as well as on the blog index page, we use Vue-Resource as dis­cussed in the Lazy Load­ing with the Ele­ment API & Vue­JS article.

Now Vue-Resource depends on Vue being loaded in order to work, so we want to make sure that Vue is loaded first, and then only after both Vue & Vue-Resource are loaded, exe­cute our code that uses them. For­tu­nate­ly, Load­JS makes this pret­ty easy to do:

<script>
    loadjs(
        [
            '{{ baseUrl }}js/vue-resource.min.js'
        ],
        'vue-resource'
    );
    loadjs.ready(['vue','vue-resource'], {
        success: function() {
            // Vue and Vue-Resource are now loaded and ready to use
        }
    });
</script>

loadjs.ready() lets you pass in an array of bun­dle names as well, and it will load each bun­dle, and then call the success func­tion once they are all loaded.

Note that although we’ve already loaded the vue bun­dle in our _layout.twig, we’re ask­ing for it to be loaded again here. If it’s already loaded, no harm, no foul, Load­JS will just return it. But with asyn­chro­nous load­ing, you nev­er assume that some­thing is loaded.

We actu­al­ly didn’t load vue in our _layout.twig, we just asked for it to be loaded. When that gets sat­is­fied is up to the vagaries of the Inter­net, which is why we uti­lize callbacks.

Link Dependency Handling

What hap­pens if we have things like jQuery plu­g­ins that depend on jQuery being loaded before they can be prop­er­ly instantiated/​executed?

The way Load­JS works is that it will just load every­thing asyn­chro­nous­ly by default, so even if the JavaScripts are in a sep­a­rate bun­dle, we can’t know in what order they will load.

So in the case of a depen­den­cy chain like this, we can sim­ply do this:

<script>
    loadjs(
        [
            '{{ baseUrl }}js/jquery.min.js'
        ],
        'jquery'
    );
    loadjs.ready(['jquery'], {
        success: function() {
            // jQuery is now loaded and ready to use
            loadjs(
                [
                    '{{ baseUrl }}js/some-jquery-plugin.min.js',
                    '{{ baseUrl }}js/some-other-jquery-plugin.min.js'
                ],
                'jquery-plugins'
            );
            loadjs.ready(['jquery-plugins'], {
                success: function() {
                    // jQuery and the jQuery plugins are now all loaded and ready to use
                }
            });
        }
    });
</script>

Using this kind of chain­ing” lets us keep all of our JavaScript load­ing asyn­chro­nous, and ensures that depen­den­cies are prop­er­ly han­dled as well.

Link Lightweight Loading

All of this saves us not just 70K or so, but also it’s one less exter­nal request we need to be doing. Here’s the Web​PageTest​.org water­fall for this site’s homepage:

One thing I will note is that Load­JS can also be used to asyn­chro­nous­ly load CSS stylesheets as well, and they work with the same depen­den­cy bundling sys­tem. This can come in handy for asyn­chro­nous­ly load­ing your full site CSS when imple­ment­ing Crit­i­calC­SS.

Load­JS can do oth­er cool stuff too; check out the Load­JS README for more. 

Hap­py loading!