One of the weaknesses of the JavaScript language is that it does not have the concept of “importing” or “including” other source files or modules. In client-side JavaScript as used in web browsers you have to include all JavaScript files you intend to use in the main HTML file like this:
<html> <head> <title>...</title> <script src="A.js"></script> <script src="B.js"></script> <script src="S.js"></script> </head> ... </body>
In this case S.js
is a page-specific JavaScript script that uses features from A.js
and B.js
.
The standard way to load other JavaScript files dynamically is to add a script
element programmatically to the head section of the document, using DOM:
function include(filename) { var scriptTag = document.createElement("script"); scriptTag.setAttribute("lang"), "javascript")); scriptTag.setAttribute("type"), "application/javascript")); scriptTag.setAttribute("src", filename); document.getElementsByTagName("head")[0].appendChild(scriptTag); }
Unfortunately this approach has some problems as well: The include file is loaded “lazily”, i.e. the next time the JavaScript interpreter hands back control to the browser. (At least it is this way in Firefox.) If, for example, a file A.js
defines a function func_a
, the following will not work:
include("A.js"); func_a();
My first solution was to use a callback, invoked when the module has been loaded:
function include(filename, callback) { var scriptTag = createScriptTag(filename); // as above scriptTag.onreadystatechange = function() { if (scriptTag.readyState == "complete") { callback(); } } scriptTag.onload = function() { callback(file); } }
This solution uses the non-standard onload
(Firefox) or onreadstatechange
(IE) events. It is invoked like this:
include("A.js", function() { a_func(); });
This solution also has several problems: It is non-standard, it is ugly (though this can’t be helped, due to the asynchronous script loading), and including a module from a module still doesn’t work. To explain the last problem, I will use a script S.js
which depends on the function a_func
defined in A.js
, which in turn depends on b_func
defined in B.js
. Now the code should look like this:
// S.js include("A.js", function() { a_func(); }); // A.js include("B.js", function() { a_func = function() { } a_func.prototype = new b_func(); }); // B.js // ...
The problem is that the callback in S.js
is not necessarily processed after the callback in A.js
, leading to a_func
being undefined.
The solution I have come up with is not particularily nice, but this can’t really be helped. Basically each module has to call a function when it has fully loaded, like this:
// A.js include.module("B.js", function() { // ... include.moduleLoaded("A"); });
The main HTML file must load the include.js
before loading any other module and modules should only be loaded via include.module
. Also my implementation uses “real” module names, e.g. a.b.c
instead of a/b/c.js
. This allows me to expand the mechanism easily, for example to add a module search path.
Leave a Reply