JS require() for browsers – better, faster, stronger

Unlike in Node.js for example, JavaScript in browsers doesn’t come with a require function to load modules dynamically. There are some implementations which try to fill this hole, but as I’ve pointed out in my previous require() post none of them really fit my needs. What I want is a lightweight solution with full CommonJS compatibility and easy handling.

I was using my first require() version only in smaller contexts last year, so I was happy even though some things were missing, but now I’m working on a more complex JavaScript framework for my current project, which gave me the impulse to improve my old code to a full featured version:

  • 100% CommonJS modules 1.0 compliant (all unit-tests passed)
  • 99% CommonJS modules 1.1.1 compliant (didn’t find any tests to verify)
  • improved security (but I’m sure it’s still not bullet proof)
  • correct relative path handling (the old version was buggy)
  • support for module bundles (I love this ^^)

I tried to improve security a bit by preventing modules from accessing the internals of the require function and by making require a read only property of window. I’m pretty sure this makes require() a bit safer, but I didn’t spent to much time on this topic to be sure.

Instead I spent several iterations on a feature I really wanted to have for the new require: Bundles (not packages). A bundle is a single file, which represents a directory with multiple modules. Hence multiple modules can be loaded with one HTTP request, which can reduce the network traffic significantly. But more on this later…

Basic usage

I wrote about the basic idea behind require() and JavaScript modules in my first require() article, so I won’t explain these concepts here again. However, if need a first impression you can visit a little example page where you can see require() in action.

If you want to use require() on your page you just have to load the tiny (~1kB) script, which defines the global require function. You can either link to the GitHub repository directly or download the latest snapshot and link to the local file (recommended for production systems):

// Load require() from a local file
<script type="text/javascript" src="./smoothie-master/base/require.js"></script>

You can now load a module either synchronously or asynchronously. The synchronous way is the one defined in CommonJS and pretty straightforward – just pass the module identifier as a parameter and require will return an object with all exports of the module:

// Load the module "myModule" from ./myModule.js
var mymod = require('myModule');
// Call the exported function "hello()"
mymod.hello();

The module itself contains standard JavaScript, but all variables and functions which should be visible to the outside are added to a special object called exports, so ./myModule.js could look like this: target=”_blank”

// "who" is only visible inside the module
var who = 'world';
// "hello()" is visible to the outside
exports.hello = function() {
	alert('Hello '+who+'!');
}

Asynchronous loading

Synchronous loading however blocks script execution until the module is loaded, so it might be better to load the module asynchronously:

// Load the module "myModule" (located at ./myModule.js)
require('myModule', function(mymod) {
	// Call the exported function hello
	mymod.hello(); 
});

As you can see a module will be loaded asynchronously when you pass a callback function as second parameter. The first parameter of the callback function is an object with all exports, just like the return value of a synchronous require call.

Module identifiers

Note: Path handling has changed so you should read this chapter if you’re switching from the old require().

The module identifier resembles the file structure of your site, where the module root path is the location of the page, which included the require.js script. So when the URL of the page is http://www.example.com/my/page/index.html the module root path will be /my/page/ and require('myModule') will try to load /my/page/module.js while require('aDirectory/aModule') will try to load /my/page/aDirectory/aModule.js.

Module identifiers not starting with a dot will be interpreted as absolute paths starting from the module root path, but relative paths are also possible. Inside the module aDirectory/aModule you can call require('./anotherModule') to load /my/page/aDirectory/anotherModule.js. You can also refer to parent directories, but you won’t be able to go higher than the module root path. Therefore require('../yourModule') and require('../../yourModule') both try to load /my/page/yourModule.js.

Alternative module root paths

Sometimes it might be necessary to use another path than the current page location as module root. Alternative paths can be defined through the global smoothie object, which contains all configuration data to keep the global scope as clean as possible. The property smoothie.requirePath is used to define the module paths. Its value has to be an array of strings, where each string describes one possible module root path (the string at index 0 is the default path). For security reasons alternative paths have to be defined before the require script has been loaded. So when you want to use the site’s root as default path and also define another alternative path this could look like this:

<script type="text/javascript">var smoothie = {'requirePath':['/','/alternative/module/root/path/']};</script>
<script type="text/javascript" src="/smoothie/base/require.js"></script>

You can read the module paths the via the require.paths property, but remember you cannot change the paths after require.js has been loaded.

To use an alternative module root path you have to prefix the module identifier with the index of the path in smoothie.requirePath. Assuming the paths are set as defined above the identifiers would be resolved as follows:

require('module1');
// loads /modules1.js (index 0 is the default, so the site root is used as path)
require('0:module2');
// loads /modules2.js (we've selected the path at index 0, so it's the same as above)
require('1:module3');
// loads /alternative/module/root/path/modules3.js (now we're using the path at index 1)

Keep in mind that this behaviour might not be 100% compatible with the CommonJS module specification. Especially prefixing the module identifier with the path index violates the specification, so I suggest not to use alternative paths when you’re writing code, which should also work with other require() implementations.

Module bundles

You will normally have several modules, which provide different functionalities for your site. Some depend on other modules, some are independent, but all are loaded using an AJAX-request. Therefore require() can generate a lot of network traffic, which usually is no problem on a development system, but something you want to prevent on a production server. You could put all your code into one big module, but that’s – well – that wouldn’t be a real module any more, eh?

This is where bundles drop in. A bundle is a set of modules, which can be loaded in advance so that all future request() calls don’t need to fetch the module code from the server. All you need to add to your code is one more require() call, so it’s easy to implement bundles just right before your site is ready to ship and you know exactly which modules are usually loaded together. This way you have flexible modules during development and low traffic in the production phase.

An example

The code without a bundle could be something like that:

// Load the module from /my/page/aModule.js
var amod = require('aModule');
// Load the module from /my/page/aDirectory/anotherModule.js
var anothermod = require('aDirectory/anotherModule');

In the example above we had two modules aModule and aDirectory/anotherModule. They can be combined into the bundle myBundle, which contains both modules. The bundle and the modules are loaded as follows:

// NOTE Load the bundle from /my/page/aDirectory.js
require('myBundle'); 
// NOTE Load the module from the bundle
var amod = require('aModule');
// NOTE Load the module from the bundle
var anothermod = require('aDirectory/anotherModule');

As promised, it’s just one more line. But how do you create a bundle? Bundles use the pre-defined variable module to return the modules they contain. According to CommonJS module contains some extra information about the loading module, but it’s no problem for a bundle to discard this information, since it wouldn’t use them anyway. Using module also keeps us from defining yet another variable, which would pollute the module scope. The code for MyBundle could look like this:

module = {
// The entry for "aModule"
'aModule': function() {
	// This is the code copied from aModule.js
	exports.greet = function() {
		return 'Hello from aModule!';
	}
},
// The entry for "aDirectory/anotherModule"
'aDirectory/anotherModule': function() {
	// This is the code copied from aDirectory/anotherModule.js
	exports.greet = function() {
		return 'Hello from aDirectory/anotherModule!';
	}
}
}

As you can see the already existing variable module is simply overwritten with a new object, which holds the code for the modules. Each property of the new object represents a module and the actual code of the module is wrapped into a function. The property name is the module identifier. You can even use relative path as you can see in the example code.

The future

I consider my require() function now as almost complete. I’m still missing a way to bundle modules which don’t share a parent directory and a little tool to generate bundles automatically, but I guess this ain’t too complicated.

Apart from that I’m planning to include require() into a lightweight library called Smoothie (look at the repository name at GitHub ;)), which contains most functionality I use regularly on my websites. But I guess it’s a bit too early to talk about that, so stay tuned, use require(), fork my repo, improve the code and post some feedback here…

PS: Once again the link to download the require() ZIP archive, since it is a bit hidden in the post.