HTML5 Zone is brought to you in partnership with:

I am a web developer at Crytek GmbH. As for my open source contributions, some were pretty popular once Sapid CMS, AOP library for PHP, few plugins for jQuery. Numerous of my works were published in specialized medias, I have been a speaker at developer conferences (e.g. PHP Conf), and published articles in magazines such as PHP Architect and PHP Magazine International Dmitry is a DZone MVB and is not an employee of DZone and has posted 8 posts at DZone. You can read more from them at their website. View Full User Profile

Modular JavaScript in the Browser with CommonJS Compiler

01.24.2014
| 10088 views |
  • submit to reddit

JavaScript was designed as a script language that is easy to embed in a larger host system and meant to manipulate the objects of the host system. With the advance of HTML5 formerly mostly static web-pages are turning into sophisticated web-applications. Now we expect JavaScript code to be scalable and modular. But how when JavaScript has no built-in facilities to combine distinct scripts?

Surely, you can insert a script element into the DOM and therefore have outer scripts loaded dynamically. Then you will have to deal with asynchronously loaded scripts and resolve all the required dependencies prior to running any depended code. In fact there are plenty of libraries that can do it for you (http://wiki.commonjs.org/wiki/Implementations). While going AMD you will quickly realize that every module makes a separate HTTP request that badly affect the application performance. Well you can use the tools like r.js to combine modules in a single build. Still it brings an entire library into the resulting code. With compiled the modules we hardly need so much of extra complexity. After the compiler enclosures the modules in unique scopes, we only need a function to manage access to these contexts. Let’s take CommonJS module format. I do love how it is implemented in NodeJS – pretty concise, but incredibly capable. It introduces module object representing a module and available in every module scope. So the module scope can look like that:

require.def( "module-id", function( require, exports, module ){
    // Module constructor
    var moduleObj = {};
    module.exports = moduleObj;
   return module;
});

Bow we need the require function to give access from module to module. Following NodeJS when require is called the first time the module is being constructed. Every second call takes the exported object from the cache. That can be implemented with the following code:

/** Define scope for `require` */
var require = (function(){
  var /**
      * Store modules (types assigned to module.exports)
      * @type {module[]}
      */
      imports = [],
      /**
       * Store the code that constructs a module (and assigns to exports)
       * @type {*[]}
       */
      factories = [],
      /**
       * @type {module}
       */
      module = {},
      /**
       * Implement CommonJS `require`
       * @param {string} filename
       * @returns {*}
       */
      _require = function( filename ) {
 
        if ( typeof imports[ filename ] !== "undefined" ) {
          return imports[ filename ].exports;
        }
        module = {
          id: filename,
          filename: filename,
          parent: module,
          children: [],
          exports: {},
          loaded: false
        };
 
        // Called first time, so let's run code constructing (exporting) the module
        imports[ filename ] = factories[ filename ]( require, module.exports, module );
        imports[ filename ].loaded = true;
        if ( imports[ filename ].parent.children ) {
          imports[ filename ].parent.children.push( imports[ filename ] );
        }
        return imports[ filename ].exports;
      };
  /**
   * Register module
   * @param {string} filename
   * @param {function(module, *)} moduleFactory
   */
  _require.def = function( filename, moduleFactory ) {
    factories[ filename ] = moduleFactory;
  };
  return _require;
}());

This design expects the compiler to replace ids given in require calls with the fully resolved filenames. So the filenames become unique identifiers for the modules. Besides, compiler must detect the require calls combinations causing infinite loops.

Using RequireJS Compiler

I released the compiler on GitHub https://github.com/dsheiko/cjsc. It is a NodeJS package that can be used as easy as it:

./cjsc main-module.js build.js

Let’s write a few modules to see what it does.

./main.js

onsole.log( "main.js running..." );
console.log( "Imported name in main.js is `%s`", require( "./lib/dep1" ).name );
console.log( "Getting imported object from the cache:" );
console.log( " imported name in main.js is still `%s`", require( "./lib/dep1" ).name );


./lib/dep1.js

console.log( "dep1.js running..." );
console.log( "Imported name in dep1.js is `%s`", require( "./dep2" ).name );
module.exports.name = "dep1";


./lib/dep2.js

console.log( "dep2.js running..." );
module.exports.name = "dep2";

After we compile the main.js module and fire up the derived build in the browser, we get the following output:

main.js running...
dep1.js running...
dep2.js running...
Imported name in dep1.js is `dep2`
Imported name in main.js is `dep1`
Getting imported object from the cache:
 imported name in main.js is still `dep1`

Well, the dependencies resolved by given ids based on relative paths, module constructors ran when required and only once – everything went as under NodeJS.

Supporting RequireJS modules

What if we call for a UMD module? Let’s try:

./main.js

console.log( "%s is running...", module.id );
console.log( "%s imports %s", module.id, require( "./umd/module1.js" ).id );

./umd/module1.js

// UMD boilerplate according to https://github.com/umdjs/umd
if ( typeof module === "object" && typeof define !== "function" ) {
    /**
    * Override AMD `define` function for RequireJS
    * @param {function( function, Object, Object )} factory
    */
    var define = function ( factory ) {
        module.exports = factory( require, exports, module );
    };
}
 
define(function( require, exports, module ) {
    console.log( "%s is running...", module.id );
    return { id: module.id };
});

The build output:

./umd.js is running...
./umd/module1.js is running...
./umd.js imports ./umd/module1.js

Everything is fine.

Automating build process

On the development environment we can have the fooling Grunt configuration:

Gruntfile.js

module.exports = function(grunt) {
  grunt.loadNpmTasks('grunt-contrib-cjsc');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.initConfig({
     cjsc: {
      development: {
        options: {
       minify: true
        },
        files: {
          "js/build.js" : "./js/modules/main.js"
        }
      }
    },
    watch: {
        options: {
          livereload: false
        },
        build: {
            files: ['./js/modules/**/**/*.js'],
            tasks: ['cjsc']
        }
      }
  });
  grunt.registerTask( "default", [ "cjsc" ]);
};

And development dependencies in package.json

"devDependencies": {
  //..
  "grunt-contrib-watch": "~0.4.4",
  "grunt-contrib-cjsc": "*"
}

Now we can make Grunt compiling the build.js automatically every time any of modules changes:

grunt watch

So as you see you can write CommonJS modules and have them running in the browser without any additional library. If you ask me about Browserify – it looks awesome. Honestly I wasn’t simply aware about that solution while building RequireJS compiler.


Published at DZone with permission of Dmitry Sheiko, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)