Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
LoadJS as a Lightweight JavaScript Loader
Here’s how to use LoadJS as a lightweight JavaScript loader & dependency manager instead of using <script> tags
Something fundamental has changed with this website. Assuming I’ve done my job properly, the cool part is you shouldn’t notice at all.
What has changed is that I’m no longer loading the JavaScripts used on this website via SystemJS, as discussed in the Using SystemJS as a JavaScript Loader article. All of the same reasons for eschewing <script> tags still apply, of course, I’m simply using a different mechanism to reap the same benefits.
So why the switch? SystemJS is a fantastic piece of work, and if you are doing heavy frontend JavaScript development with Vue, React, or what have you, the ability to load modules in a variety of formats and dynamically transpile them is pretty 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 ability to load our JavaScripts asynchronously, and we still want some modicum of dependency management, so that our JavaScripts all load in a deterministic order.
LoadJS is a handy solution for simple frontend JavaScript loading requirements. It loads everything asynchronously, it provides the grouping together of JavaScripts in “bundles”, and it provides callbacks when these bundles are loaded, just like SystemJS and RequireJS.
LoadJS doesn’t provide all of the bells and whistles of SystemJS, but I don’t need them for this project. And it’s also 0.7K vs. SystemJS weighing in at 70K (yes, LoadJS is just 1/100th of the size).
So what we do lose? We lose the ability to do any dynamic transpiling of our JavaScripts. And it only works with “modern” browsers (defined as IE 9+). For my purposes, both of these requirements are perfectly acceptable. If you require dynamic transpiling or loading of modules in a variety of formats, use SystemJS. If you require supporting much older browsers, use RequireJS.
But please use something for async loading and dependency management of your JavaScripts! All of this will become a thing of the past whenever the Module Loader Spec is finalized and implemented. But we live in the present!
Link A Lean, Mean, Loading Machine
What I love about LoadJS is that it’s lean and mean. As mentioned, it weighs in at a spritely 0.7K before being gzip’d.
This allows us to just inline the LoadJS 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 directive works just like Twig’s include, but it doesn’t parse the file as a template, it just includes the raw content of the file. The only requirement is that the file exists somewhere in your templates directory tree.
The register-service-worker.min.js is discussed in the ServiceWorkers and Offline Browsing article.
We include LoadJS early on in our <head> so that it’s available everywhere, as per the recommended 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 available, how do we use it? It’s pretty simple, we just define a “bundle” by giving it a list of URLs and a name to assign to the bundle. For instance, in my main _layout.twig I define a bundle for Vue, something that’s used globally 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 downloaded and cached early. This is a very simple bundle, which has only one JavaScript resource, but you can put as many as you like into the array you pass to loadjs(). The bundle name you pass in (vue in this case) is just how you refer to it later on when you want to load it.
The loadjs.ready() function asynchronously loads in all of the JavaScripts in our vue bundle by adding them to the document.head as <script src="" async> tags, which in turn causes the web browser to load them.
When all of the JavaScripts in a bundle are loaded in, it calls the success function, 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 pretty easy, but what about on our Blog detail pages, where we need a few additional resources? No problem, 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 lazysizes & prism are loaded, and once they are, we call Prism.highlightAll(); to tell prism to do it’s thing highlighting our code samples.
For our blog archives that appear on the bottom of each blog entry page as well as on the blog index page, we use Vue-Resource as discussed in the Lazy Loading with the Element API & VueJS 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, execute our code that uses them. Fortunately, LoadJS makes this pretty 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 bundle names as well, and it will load each bundle, and then call the success function once they are all loaded.
Note that although we’ve already loaded the vue bundle in our _layout.twig, we’re asking for it to be loaded again here. If it’s already loaded, no harm, no foul, LoadJS will just return it. But with asynchronous loading, you never assume that something is loaded.
We actually didn’t load vue in our _layout.twig, we just asked for it to be loaded. When that gets satisfied is up to the vagaries of the Internet, which is why we utilize callbacks.
Link Dependency Handling
What happens if we have things like jQuery plugins that depend on jQuery being loaded before they can be properly instantiated/executed?
The way LoadJS works is that it will just load everything asynchronously by default, so even if the JavaScripts are in a separate bundle, we can’t know in what order they will load.
So in the case of a dependency chain like this, we can simply 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 “chaining” lets us keep all of our JavaScript loading asynchronous, and ensures that dependencies are properly handled as well.
Link Lightweight Loading
All of this saves us not just 70K or so, but also it’s one less external request we need to be doing. Here’s the WebPageTest.org waterfall for this site’s homepage:
One thing I will note is that LoadJS can also be used to asynchronously load CSS stylesheets as well, and they work with the same dependency bundling system. This can come in handy for asynchronously loading your full site CSS when implementing CriticalCSS.
LoadJS can do other cool stuff too; check out the LoadJS README for more.
Happy loading!