Sebastian Rittau's Blog

Saturday
Nov 18 2006

Loading Modules in JavaScript

JavaScript, Programming

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.

Sample implementation

Comments

Struggling with this problem

by Paul Nahay

Friday, 2006-11-24 22:09

Thanks for this. I don't quite understand how to use your code, esp. since you call an undefined function:
createScriptTag(filename);

But I've wasted probably a week trying to get Firefox to run some JS code of mine (it won't correctly), when all works fine in IE.

Paul

by Sebastian Rittau

Friday, 2006-11-24 23:44

createScriptTag is just another name for the include function I defined at first. Sorry, that's not very clear from the stuff I wrote. By now I use a different solution, anyway. (See http://www.rittau.org/blog/20061120-01)

I can feel your pain regarding the Firefox/IE porting. I've had similar problems, only the other way round.

Better Way

by seant23

Wednesday, 2007-07-11 17:26

The problem with they way your using it that the included script won't get parsed untill the current script is finished being parsed. here is a better solution for realtime inclusion.

http://www.exit12.org/archives/12

Comments for this article have been disabled.