JavaScript Module Loader
April 27, 2009 by
I have been crafting a lightweight JavaScript module loader for some time now. I was looking for something that wasn't too complicated.
While I admire the work done by JSModule, its design was a little too specific for my needs, so I decided to craft my own. This loader also includes a namespace() function for making the connection between your module and the global namespace. This might not make a lot of sense, so let me explain how to make a module.
Scripts are downloaded and included using the include() function. The script must be located on the same domain as the document, since this loader relies on the XMLHttpRequest() object.
Unlike some other loaders, which require that you use a callback function which will be executed once the script is sure that the requested file has been evaluated, this script allows you to include() your script and start using it immediately, just like many other languages. The reason that it can do this is because it uses the much hated eval() function to bring the requested script to life.
However, if I simply allowed scripts to be eval'd, any variables local to that script would be thrown straight into the global namespace of the current environment. Not only is this bad practice, but it also likely isn't desirable. Instead, I have decided to force all scripts into an anonymous scope. Essentially, your script is seen by the browser like this:
(function () {// your code here...})();
If you're not familiar, the significance of this notation is that the anonymous function is enclosed in parentheses, which gives it an anonymous scope. It does not execute in the global namespace. Instead, it is encapsulated in this anonymous namespace, and it is up to you to bridge the gap between the global namespace and this anonymous namespace using closure and the included namespace() function.
If you're not familiar with closure in JavaScript, go ahead and read up on it before trying to craft modules. However, the gist of it is that any functions declared in the context of another function but remain accessible to the window still have access to variables local to the scope the function was declared in. This allows us to have private variables and functions in our modules that don't pollute the global namespace, but also have public functions, accessible to the global namespace, that also have access to those local variables and functions from the parent scope.
The Chicken Or The Egg
If this isn't making any sense, scroll down and examine the source code for the loader: it itself is a module. Notice that there are functions declared within the scope using the function keyword. These functions are not accessible to the global namespace, however, they are accessible to the functions exposed to the world using the window.functionName notation.
I do not recommend you expose your functions using this notation, but I think it demonstrates my point. We want to keep the global namespace as clutter-free as possible, so it would behoove you to use the included namespace() function to benefit from namespacing.
The loader automatically encapsulates your script in an anonymous scope, however for readability purposes, I also write all of my modules using this notation in the script itself. You could just as easily leave out the parenthesis and anonymous, self-calling function, but it will be more obvious what the file is, and it will be easier to conceptualize that which is private and what is public when writing lengthy modules if you leave them in.
Here's a general outline of a module:
(function () {// namespace returns reference to namespacevar ns = namespace("com.andrew");// Private functionfunction priv() {alert("Private!");}ns.pub = function () {priv();};})();
You could alternatively use the YUI Module Pattern, which will make your code easier to convert to a standalone module in an environment where you can't rely on a global namespace() function. However, in environments where you can't count on the loader, feel free to include the namespace() function below in your module.