Web Dev Ramblings

A lightweight alternative to Require JS

Published Monday, July 25, 2016

Require JS is one of the most used methods online to dynamically load JavaScript on the client, however it is probably more than most of us will ever need, and it is also quite large.

While Require JS is a reasonable solution for some use cases, for most it is probably a lot more complicated than what you need. And as it requires a 15.5KB script file (too big to inline) to use, why not opt for something a little simpler that will do basically the same thing.

An alternative implementation

Below is an implementation of dynamic script loading at 1.21KB without minimization and compression. It allows you to load any number of scripts, asynchronously, thus mimicking one of the most important features of Require JS but in a much simpler and lighter package.

Like Require JS, you can load a large number of files at one time, without worrying about which file is loaded first as long as initialization is done in the callback function where all required files have been loaded. ( Of course, whenever possible, prefer instead to combine all your script files into one file whenever possible )

var JS = { toLoad: [], loaded: [], Load: JS_Load, OnScriptLoaded: JS_OnScriptLoaded }; function JS_Load(scripts, onload) { for (var i = 0; i < scripts.length; ++i) { if (this.loaded.indexOf(scripts[i]) != -1) { scripts.splice(i, 1); i -= 1; } } if (scripts.length == 0) { if (onload) { onload(); } return; } this.toLoad.push({ scripts: scripts, onload: onload }); for (var i = 0; i < scripts.length; ++i) { var src = scripts[i]; var e = document.createElement("script"); e.src = src; e.setAttribute("srcFromLoad", src); e.async = true; e.onload = function (ev) { JS.OnScriptLoaded(ev.target.getAttribute("srcFromLoad")); }; document.getElementsByTagName("head")[0].appendChild(e); } } function JS_OnScriptLoaded(src) { this.loaded.push(src); for (var i = 0; i < this.toLoad.length; ++i) { var toLoad = this.toLoad[i]; for (var j = 0; j < toLoad.scripts.length; ++j) { if (toLoad.scripts[j] == src) { toLoad.scripts.splice(j, 1); if (toLoad.scripts.length == 0) { if (toLoad.onload) { toLoad.onload(); } this.toLoad.splice(i, 1); i -= 1; } break; } } } }

Or in minimized form 893 bytes on a single line:

function JS_Load(t, o) { for (var r = 0; r < t.length; ++r) -1 != this.loaded.indexOf(t[r]) && (t.splice(r, 1), r -= 1); if (0 == t.length) return void (o && o()); this.toLoad.push({ scripts: t, onload: o }); for (var r = 0; r < t.length; ++r) { var e = t[r], a = document.createElement("script"); a.src = e, a.setAttribute("srcFromLoad", e), a.async = !0, a.onload = function (t) { JS.OnScriptLoaded(t.target.getAttribute("srcFromLoad")) }, document.getElementsByTagName("head")[0].appendChild(a) } } function JS_OnScriptLoaded(t) { this.loaded.push(t); for (var o = 0; o < this.toLoad.length; ++o) for (var r = this.toLoad[o], e = 0; e < r.scripts.length; ++e) if (r.scripts[e] == t) { r.scripts.splice(e, 1), 0 == r.scripts.length && (r.onload && r.onload(), this.toLoad.splice(o, 1), o -= 1); break } } var JS = { toLoad: [], loaded: [], Load: JS_Load, OnScriptLoaded: JS_OnScriptLoaded };

How to use

Include either of the above code snippets on every page where you need this capability. Then use JS.Load to load external script files only when you actually need them, like so:
JS.Load([ '/js/one.js' ]);
One.js
function InitOne() { alert("One"); InitTwo(); InitThree(); } JS.Load([ '/js/two.js', '/js/three.js' ], InitOne);
Two.js
function InitTwo() { alert('Two'); }
Three.js
function InitThree() { alert('Three'); }

The result of this example would be loading three different JavaScript files and when they have all been loaded calling a function in each giving you an sequence of alerts "One", "Two", "Three"

This is great, but...

For most of you, your website is probably simple enough that even this is a lot more complicated than what you actually need. For most sites, you can most likely keep the script load down to a level where you only need to load a single script file. Make sure to set either defer or async on the script tag and you have what is probably the optimal solution for most use-cases.