Problems

  • Web sites are turning into Web apps
  • Code complexity grows as the site gets bigger
  • Developer wants discrete JS files/modules

Solution

  • Some sort of #include/import/require
  • Ability to load nested dependencies

CommonJS

The dominant incarnation of this specification is Node.js modules (Node.js modules have a few features that go beyond CJS).

Characteristics:

  • Compact syntax
  • Designed for synchronous loading
  • Main use: server

Basic example

// Works sync and hurts page performance
var Animal = require('Animal');
var zoo = [];

// exporting `Zoo` module
exports.Zoo = function() {
    return {
        getAnimalsCount: function() {
            return zoo.length;
        },
        addAnimal: function(name) {
            // Will give error, if require were async
            zoo.push(new Animal(name));
        }
    };
};

AMD - Asynchronous Module Definition

AMD specification is implemented by Dojo Toolkit, RequireJS etc.

Basic exmaple

// Works async
define('Zoo', ['Animal'], function(Animal) {
    var Animal = require('Animal');
    var zoo = [];
    
    // returning `Zoo` module
    return {
        getAnimalsCount: function() {
            return zoo.length;
        },
        addAnimal: function(name) {
            zoo.push(new Animal(name));
        }
    };
})

Conditional loading example

define('foo', function(require) {
    if (false) {
        require(['https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'], function () {
            console.log(jQuery);
        });
    }
});
require(['foo']);

Issues

For the project of medium complexity, HTTP overheads for asynchronous downloading of single Javascript files are dramatic. Therefore, RequireJS advises developers to proceed with a bundling operation of the various modules using their command line tool.

Bundling process

The process parses the files begining at a specified entry-point to reconstruct the dependencies on the basis of the define() calls that it finds along the way. It is then able to organize and concatenate all of the necessary files in a single file i.e. bundle.js which we will then be included in our HTML.

UMD - Universal Module Definition

Since CommonJS and AMD styles have both been equally popular. This has brought about the push for a universal pattern that supports both styles.

The pattern is both AMD and CommonJS compatible, as well as supporting the old-style global variable definition.

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery', 'underscore'], factory);
    } else if (typeof exports === 'object') {
        // Node, CommonJS-like
        module.exports = factory(require('jquery'), require('underscore'));
    } else {
        // Browser globals (root is window)
        root.returnExports = factory(root.jQuery, root._);
    }
}(this, function ($, _) {
    //    methods
    function a(){};         // private
    function b(){};         // public
    function c(){};         // public

    return {
        b: b,
        c: c
    }
}));

Browserify

Unlike RequireJS, Browserify allows you to write your client-side application making use of CommonJS’s synchronous loading (using Node.js). How is this possible?

  • Bundling similar to require.js browserify app.js --outfile bundle.js
    
// let's take a look at the generated `bundle.js`
function debundle(data) {
  var cache = {};
  var require = function(name) {
    if (cache[name]) { return cache[name]; }
    var module = cache[name] = { exports: {} };
    data.modules[name](require, module);
    return module.exports;
  };
  return require(data.entryPoint);
}

debundle({
  entryPoint: "./app",
  modules: {
    "./app": function(require, module) {
      var calculator = require('./calculator');
      console.log(calculator.sum(1, 2));
    },
    "./calculator": function(require, module) {
      module.exports = {
        sum: function(a, b) { return a + b; }
      };
    }
  }
});

The content of every module, including the entry-point, can be requested during runtime by being passed to debundle() which implements the module.exports/require - CommonJS mechanism on the client side.

References