Node.js require() for your browser

Update: This article is obsolete. Check out my post “JS require() for browsers – better, faster, stronger” for an improved version, which is CommonJS compliant, more secure, handles relative paths correctly and supports module bundles. The require function described here is obsolete.

Everyone who has worked a bit with JavaScript will sooner or later wish for an easy way to load a script directly from another script (similar to #include in C or require() in PHP). Over time frameworks like RequireJS and some other code snippets bubbled up to the surface, but they all have some drawbacks: They either require quite a lot of extra code, only allow asynchronous loading (this is not always a good idea) or don’t separate the module from the rest of the code, which makes porting kinda difficult.

Then came Node.js with its easy to use module concept, which needs nothing more than a simple function call to load a JavaScript file into its own namespace. When I started to use Node.js this concept felt a bit odd at first, but once I got used to it I loved it an started to look for a way to bring Node.js’ require() to the browser. I stumbled upon Browserify pretty fast, but didn’t take a closer look, since it simply looked like a bit too much for my use-case and all this command-line stuff just didn’t feel right.

In the end I started to implement my own require function for browsers, which resembles most of the features implemented in Node.js and also allows asynchronous loading. For the lack of creativity I simply called the project Require(). However, I’m sure it lacks some features of the bigger frameworks, but it’s just a small script (ZIP archive) and pretty easy to use as you can see on the example page, so I think it’s worth a try.

The Node.js way

First of all I’ll give a short introduction into the way Node.js handles modules. I’m just explaining the very basics, so you can skip this section, if you’re already used to it.

As I’ve already mentioned require() is used to load files. For normal JavaScript files this means that they are executed when they are loaded for the first time and that’s it. The code will be executed in its own closure, so it won’t interfere with the rest of your code (e.g. identical variable names are no problem). The only way to return something to the outside world is by modifying a special object called exports, which will be the return value of the require() call. A JavaScript file, which uses exports to return a function or variable to the outside world, is called a module.

Well, that’s not the whole story, but everything you need to know to understand my require() implementation. If you want to know all details you should read the Node.js documentation on modules.

The browser way

The require implementation for browsers works pretty much the same way the Node.js implementation does. There are some smaller differences, but let me show how to use require() in your browser first:

<html>
<head>
	<script type="text/javascript" src="require.js"></script>
</head>
<body>
	<script type="text/javascript">
		var greeting = require('./greeting.js');
		greeting.sayHello();
	</script>
</body>
</html>

The only file we need to load the usual way is require.js which defines window.require(). After that we can load the module greeting.js as we would do in Node.js. The variable greeting holds all stuff exported by the module so you can think of it as a namespace. As you can see calling an exported function, sayHello() in this case, works the same way as calling the method of an object.

The code in greeting.js could look like this:

exports.sayHello = function() {
	alert('Hello World!');
}

Again no surprises here – everything works like in Node.js. We implement the sayHello() as a method of exports and that’s it.

Asynchronous loading

Asynchronous module loading is a feature that Node.js is – AFAIK – not capable of, but it’s quite handy in a lot of web-development situations. Luckily it’s quite easy to tell our little require function for the browser to load a module asynchronously:

<html>
<head>
	<script type="text/javascript" src="require.js"></script>
</head>
<body>
	<script type="text/javascript">
		require('./greeting.js', afterRequire);
		function afterRequire(greeting) {
			greeting.sayHello();
		}
	</script>
</body>
</html>

As you can see the code hasn’t changed too much. All we have to do is to add the name of a callback-function as a second parameter to the call. require() will then load the module asynchronously and call the callback-function with the namespace-variable as its only parameter afterwards.

The module itself doen’t need any changes for asynchronous loading, so it contains the same code as in the first example.

Implicit path names

Node.js has some rules for module paths, which try to complement incomplete pathnames to a full path. require() tries to resemble most of these rules, but some won’t make a lot of sense in a web-development context so I dropped them. However, here a short overview of the implemented rewrite-rules:

  • /some/path/module ⇒ /some/path/module.js
  • /some/path/module.js ⇒ /some/path/module.js
  • /some/path/module/ ⇒ /some/path/module/index.js
  • ../module ⇒ ../module.js
  • ../module.js ⇒ ../module.js
  • ../module/ ⇒ ../module/index.js
  • module ⇒ /js_modules/module.js
  • module.js ⇒ /js_modules/module.js
  • module/ ⇒ /js_modules/module/index.js

As you can see absolute and relative paths are possible and if no path is given require() will look for the module in the global js_modules directory. As long as you specify an extension the last part of the path will be interpreted as a file, otherwise it will be interpreted as a directory and require() will try to load the file index.js within this directory.

JSON data

Our require function is capable of handling JSON encoded data, too. Every response with the content-type application/json will be interpreted by the JSON parser. The server is responsible for sending the right handler, so you might have to some configuration stuff here. Joshua Gourneau’s post on how add a JSON type to an Apache/Ubuntu server might be a good hint here (it’s easy :)).

Apart from that you should remember that require() will always return the complete JSON object, so you don’t have to put your data in an exports child-object or something. Here’s an example:

<html>
<head>
	<script type="text/javascript" src="require.js"></script>
</head>
<body>
	<script type="text/javascript">
		var greeting = require('./greeting.json');
		alert(greeting.helloStr);
	</script>
</body>
</html>

The JSON data in greeting.json could look like this:

{"helloStr":"Hello World!"}

Asynchrounous loading works of course too, so this is a nice way to access a database through a JSON interface like the one Thomas Frank described in his MySQL to JSON post.

Little helpers

You can call require.resolve(module) to get the actual full path of a module without loading the module.

You can access the module cache array via require.cache, a single module can be accessed via require.cache[path]. This is quite handy when you want to remove a module from the cache to ensure that the next require call reloads the module-file from the server.

Conclusion

I’ve used require() in two of my projects so far and even with dynamic page loading and other stuff I never had problems with it. In fact it changed a lot the way I use JavaScript in my projects now – and I’m pretty sure to a better way ;)

I’ve tried to make my implementation as compatible as possible to the Node.js version, but I’ve never tried to load a module written for Node.js in a browser. For that reason there might be some incompatibilities (especially with paths), but I think they should be fixable. It would be nice if you could post a comment here if you managed to load a Node.js module, so others might benefit from you experience.

Here’s the link to the the example page once again and finally Require() on GIST (minified version in the ZIP archive).

Important: The require function described here is obsolete. Check out my post “JS require() for browsers – better, faster, stronger” for an improved version, which is CommonJS compliant, more secure, handles relative paths correctly and supports module bundles.