2013-05-13

Sharing modules between Node.js and browser, my way

I have been playing with JavaScript for a while now and using node.js and browsers to run my code. I have got a bit frustrated that I need to write my components either for node.js or for browser. It is quite annoying that the same code does not run easily in both environments and common pattern for modularization is not available out of the box.

Node.js has quite nice module implementation where components can require other components and each module defines its API by exporting the public parts. Of course this module mechanism is not available when running inside browsers. Instead in browser-land there is a fairly common pattern to separate your modules into a self executing unnamed function that encapsulates the private parts and returns your module API that is assigned to a global variable defining your namespace. If you wish something more sophisticated, there are many other approaches to provide modules in browsers.

I started to check few different module libraries, but for a reason or another I did not like any of those. Sometimes use did not match with node.js. Sometimes you had to use extra components in node.js to share the code. Sometimes defining modules was bit more complicated than I hoped. Most of the time I did not like those, because those were not invented by me ;)

To be honest, real reason to write my own module implementation is really to understand the problem. At the same time I wanted to find a nice pattern for writing the component so that it works for both: node.js and browsers.

Code related to this article that implements my module library is freely available in github.

Pattern for writing modules

To use my module implementation, you need to follow a fairly simple pattern for encapsulation. This is to prevent from polluting global namespace in browsers. What you need to do You need to wrap your component inside a self executing function that encapsulates your component:


(function(exports) {
    var myLocal = "this value is visible inside the module";
    
    function callMe() {
        return myLocal;
    }

    exports.func1 = callMe;
})(typeof exports !== "undefined" ? exports : this.myModule = {});

To explain a bit, the syntax (function(exports) { ... })(...); creates a closure that will be executed automatically when your JavaScript file is evaluated. In this pattern, we pass the exports object to the module as a parameter and if exports is not available we pass this.myModule where this is most probably window object and myModule becomes a global object.

Often in node.js you want to pass all exports as an object. In this case you need to use module object instead of exports and set modules's exports property. See the example below:


(function(module) {
    var myLocal = "this value is visible inside the module";
    
    function callMe() {
        return myLocal;
    }

    module.exports = {
        func1: callMe,
        version: "1.0"
    };
})(typeof module !== "module" ? module : null);

In this case if module is not available, we will pass null that will eventually cause code to throw error. In the end, it is just fair because writer of the component expects module to exist and mini-module.js being used.

Using from API from other modules

As in node.js, you can use require(…) function to load other modules that your module will use. Keep in mind that you must not call require outside your module's encapsulating function or you will pollute global scope. You can use either relative paths or module names to require other modules:


(function(module) {
    var otherModule1 = require("../../otherModules/otherModule1.js");
    var otherModule2 = require("otherModule2");

    var myLocal = "this value is visible inside the module";
    
    function callMe() {
        return myLocal;
    }

    module.exports = {
        func1: callMe,
        version: "1.0"
    };
})(typeof module !== "module" ? module : null);

Using relative paths should be fairly clear, but using module names requires a bit more explaining. In node.js when you use npm to install other modules, you can later refer to these modules by their name and you do not need to know actual location of the module. Node.js uses certain module load paths to find modules that are installed under node_modules folders and these modules can be referred just by using module name. This causes some challenges to replicate this behaviour in browser.

Using modules in browsers

Now we have nice pattern for encapsulation to create modules, way to define public API and way to require other modules. Next we need a way to include our components in the web application so that things still continue to work with node.js. What I do not like so much in other module implementations, is the way how those load scripts dynamically at the execution time by injecting script elements or sometimes doing something as dangerous as loading script file with AJAX request and evaluating the content. There are definite pros in dynamic script element injection, but it requires separate JavaScript blob to be included to define modules you want to load and their dependencies. This approach allows calculation of module load order so that modules will be load in right order based on their dependencies, but on the other hand I have not seen implementation that works with node.js out of the box.

I like more the approach where all scripts are defined in HTML as static script elements so that it is easy for the developer to see what gets loaded and in which order. The main challenge is to create export mechanism that maps script elements to JavaScript modules. Luckily modern browsers allow this to be done by using document.currentScript that will tell script element matching with currently running JavaScript file. Use of document.currentScript is abstracted into internals of mini-module.js so that when you use module or exports this mapping is created and also possible module name mapping is created based on data-module-name attribute in script element. There is also a fallback mechanism included for some browsers that do not support document.currentScript. Though the fallback mechanism is tested only with Chrome and Firefox, but it should be fairly easy to add support for other browsers too.

To use mini-module.js, you need to add script element in your HTML file that loads mini-module.js and it needs to be located before any other script elements that use module, exports or require. This script element that loads mini-module.js must not be load asynchronously. When loading your own modules, you need to be careful with order of script elements. If you have inter-module dependencies, you need to make sure you load everything in right order. Also be careful with use of asynchronous loading (async attribute of script element). If you load your modules asynchronously, there is no guarantee for the order when your JavaScript will be executed and if your module requires another module, there is no guarantee that the required module is available. Easiest way is not to use async attribute with any module that is required by another one or at least do not use async attribute with modules that are required by other modules.

The purpose of this module concept is to be as simple as possible and provide development experience allowing to write modules without installing extra modules in node.js. This does not provide fancy features like dependency calculations or dynamically loaded dependency chains. Also minifying all JavaScript into a single file does not work with mini-module.js. If you want to minify your JavaScript, I suggest minifying each file separately.

Code is available in github. Under test folder, you can find example index.html loading few modules that are also load by node.js script. Feel free to fork the code and hack as much as you like.