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.

20 thoughts on “JS require() for browsers – better, faster, stronger”

  1. Pingback: Node.js require() for your browser | Pixels vs. Bytes
  2. Hi,

    I am trying to convert a small library currently based on closure to your require.js (the name is a bit unfortunate because of the existing requirejs project). If I understand this correctly, you support setting properties of exports, but not replacing module.exports with a new object. Is that correct? If so, do you have any plans on changing that?

    The reason why I am interested in module.exports is that it would allow me to just replace

    goog.provide(“mylib”);

    with

    mylib = module.exports = exports = {};

    Stefan

    1. Yes you’re right. Replacing the whole module.exports object itself if currently no possible by design – I never thought this would be needed ;) However, I have a rough idea how I could implement such functionality, but I’m quite busy right now, so I don’t think it will make it into the public repository before end of June. I will reply to your comment here again, when it’s done, so it might be a good idea for you to subscribe to the comment feed of this post (if you haven’t already done so).

      PS: Yap, the naming of my original require project was a bit unfortunate ;) I never thought it would become so popular, so I didn’t care much about the name. I now merged the whole require code along with some other stuff into a repository called Smoothie, so you might refer to it as “Smoothie require” or something like that.

    2. Hi,

      I’ve updated the require function; replacing the exports object as a whole is now possible! Here is a small nonsense example:

      var MyExports = function() {
      	this.greet = function() {
      		alert('Hello world!');
      	}
      }
      
      exports = new MyExports();

      Hope, this also works in your case – any feedback will be appreciated ;)

      1. Can you change it to module.exports as it described in commonjs specification. Redefining exports itself does not work in nodejs (because exports itself is a link to module.exports).

        1. I never found a clear documentation about whether to use module.exports or just exports. Anyway, compatibility is a matter so I’ll try to fix that. Unfortunately this may take some time, since I’m pretty busy right now.

  3. Hi Torben

    Is there a reason for you to use ‘exports’ instead of ‘module.exports’, which would be more compatible with existing node modules?

    Best regards,

    Erc

    1. Hej Eric,

      there is no specific reason, why I use “exports” instead of “module.exports” in my examples. However, you can also use “module.exports” with my require if you like.

      Bai
      Torben

  4. Will you consider releasing this under the MIT License so it can be used and included within closed-source projects?

    1. Well, it’s released under the LGPL so you should be able to use it in closed source projects without any problems.

  5. Pingback: Using Node.js modules in HTML - Ziebold Gyan
  6. Hi,
    exactly what I was looking for, so great thanks.
    But I’m unable to get it to work in Safari 7.1.
    How can I send you my minimal test project that breaks?
    Eric

      1. Hi,
        the gist is here:
        https://gist.github.com/ericvergnaud/af0a0eddff042cd5f5b6
        please replace the ‘_’ in the file names by a directory structure:
        test.html
        root/
        module.js
        leaf/
        module.js
        klass.js

        1. Hej Eric,

          it seems that you’re using modules the wrong way. You have to use the exports variable, if you want to make something visible outside of the module. I’ve fixed your example and everything seems to run as expected now. Here’s the code: http://pixelsvsbytes.com/files/eric.zip

          However, I suggest that you read the Node.js documentation on modules to make sure that you’ve understand the underlying principles correctly.

          Baibai
          Torben

          1. Hi Torben,

            thanks very much for your help.
            I’ve used Node.js extensively recently, in a project with 60 modules, hence why I’m looking for ‘require’ support in a browser.
            Indeed I forgot to provide the exports in my test, sorry for bothering you with that.
            What I was actually exploring is the bundle feature you mention (hence why my folder level scripts are called module.js).

            Cheers and thanks,
            Eric

  7. Hi, Torben. I really like what you’ve down with the in-browser commonjs require functionality. It’s about the only way I’ve found to do simple commonjs includes while using Polymer (due to Polymer’s Vulcanize front-end build tool not supporting commonjs).

    One request/question I have. The require functionality seems to work great with single js file references. However, when require a Js file that requires another js file relative paths don’t work as expected, so I’m unable to do chain dependencies when the javascript files are in different folders.

    This does not work in browser using your lib, but works fine using WebPack.
    /myapp.html
    /js/lib/apps/myapp.js
    js/lib/jquery/jquery.js
    myapp.html –> require(./js/lib/apps/myapp.js);
    myapp.js –> require(../../jquery/jquery.js);

    Workarounds:
    1. Put all JS files in a single folder
    2. Make all JS file paths relative to the HTML page they are used in

    Question: Is there anyway to make relative paths work properly, so the nested require statements and dependency chains work like commonjs (e.g. compiles with front-end build tools, etc.)?

  8. Hello,

    I guess there is an bug either on this page or on the library.
    Instead of requirePaths the library expect requirePath.

    Thx
    Benjamin

  9. Hi,

    Great work!

    We’re a research group and we needed to reuse Node.js code in a client front end. Google searches didn’t bring us to your website at the time… so we developed our own require() solution.

    We called it nodular.js, it works great but we’d be glad to hear comments from the community!

    You could give it a try at https://github.com/salathegroup/nodular.js

    It comes with a live example here: https://salathegroup.github.io/nodular.js/examples/

Leave a Reply to Eric Cancel reply

Your email address will not be published. Required fields are marked *