Javascript modules

March 20th, 2016 - Bonn

I’ve been using javascript modules of different kinds without understanding the terminology and approaches, I wanted to make some notes about it:

We’ve using some sort of modules before with Coffeescript/Angular in Rails projects. Often we just had an object App defined on the global scope, to which we attached the given module:

  # setup.js

  window.App = {}

  # modules/myModule.js

  class App.myModule
    foo = 1
    privateFunction = ->
      foo++;
    @publicFunction: ->
      privateFunction();
      console.log foo

  # otherfile.js

  App.myModule.publicFunction(); # 2
  App.myModule.publicFunction(); # 3

which compiles to

window.App = {};

App.myModule = (function() {
  var foo, privateFunction;

  function myModule() {}

  foo = 1;

  privateFunction = function() {
    return foo++;
  };

  myModule.publicFunction = function() {
    privateFunction();
    return console.log(foo);
  };

  return myModule;

})();

App.myModule.publicFunction(); // 2
App.myModule.publicFunction(); // 3

With the rails sprockets bundling, the different js files were compiled in the order specified in the manifest. There was not a way to control dependencies or load part of it asynchronously. The compiled coffee files were wrapped in a IIFE to provide isolation.

Our process has been very similar with angular

  # setup file

  angular.module('myApp', [dependencies])

  # directive file

  angular.module "app"
    .directive "myDirective", (dependencies)->
      // ...


Module Patterns

From JavaScript Modules: A Beginner’s Guide: The Module pattern is used to mimic the concept of classes (since JavaScript doesn’t natively support classes) so that we can store both public and private methods and variables inside a single object. That allows us to create a public facing API for the methods that we want to expose to the world, while still encapsulating private variables and methods in a closure scope.

There’s very good explanation on the Learning JavaScript Design Patterns from Addy Osmani

This is the classic pattern:

var testModule = (function () {
  var counter = 0;
  return {
    incrementCounter: function () {
      return counter++;
    },
  };
})();

// Increment our counter
testModule.incrementCounter();

He goes to show a common template:

var myNamespace = (function () {

  var myPrivateVar, myPrivateMethod;

  // A private counter variable
  myPrivateVar = 0;

  // A private function which logs any arguments
  myPrivateMethod = function( foo ) {
      console.log( foo );
  };

  return {

    // A public variable
    myPublicVar: "foo",

    // A public function utilizing privates
    myPublicFunction: function( bar ) {

      // Increment our private counter
      myPrivateVar++;

      // Call our private method using bar
      myPrivateMethod( bar );

    }
  };

})();

Variation: Import mixins

This shows how to pass and alias globals (e.g jQuery, Underscore) to our module:

// Global module
var myModule = (function ( jQ, _ ) {

    function privateMethod1(){
        jQ(".container").html("test");
        console.log( _.min([10, 5, 100, 2, 1000]) );
    }

    return{
        publicMethod: function(){
            privateMethod1();
        }
    };

// Pull in jQuery and Underscore
})( jQuery, _ );

myModule.publicMethod();

Variation: Exports

// Global module
var myModule = (function () {

  // Module object
  var module = {},
    privateVariable = "Hello World";

  function privateMethod() {
    // ...
  }

  module.publicProperty = "Foobar";
  module.publicMethod = function () {
    console.log( privateVariable );
  };

  return module;

})();

Revealing Module pattern.

We define all of our functions and variables in the private scope and return an anonymous object with exposes the ones meant to be public: public.

  var myRevealingModule = (function () {

    var privateVar = "Ben Cherry",
       publicVar = "Hey there!";

    function privateFunction() {
       console.log( "Name:" + privateVar );
    }

    function publicSetName( strName ) {
       privateVar = strName;
    }

    function publicGetName() {
       privateFunction();
    }


    // Reveal public pointers to
    // private functions and properties

    return {
       setName: publicSetName,
       greeting: publicVar,
       getName: publicGetName
    };

  })();

Globals, CommonJS, AMD, UMD

With the previous patterns, we rely on a global variable that sets the namespace for that given module. This is known as the Globals option. In the last years, there’s been some approaches developed to account for dependency order and avoid namespace collisions, by implementing a convention that avoids going through the global scope.

CommonJS

A CommonJS module looks like this:

  // myModule.js
  function myModule() {
    this.foo = function() {
      return 'foo!';
    }
  }

  module.exports = myModule;

module is an special object that will keep a reference to the exported content provided in myModule.js in a way in which it can be required be calling require('myModule') in another file. This works on the server, where those requires will load those files synchronously, this is the Node.js approach. The reference passed to module.exports doesn’t have to be a function:

  // foo.js
  var Foo = "Hey Foo"
  module.exports = Foo;

  // bar.js
  var Bar = {
    sayHi: function(){ console.log("Hi!") }
  }
  module.exports = Bar;

  // otherFile.js
  var foo = require('foo');  
  var bar = require('bar') ;
  console.log(foo);
  bar.sayHi();

AMD = Asynchronous Module Definition

This is the asynchronous variant for the browser. A couple of examples:

  // myModule.js
  // the empty array correspond to the dependencies (none)
  define([], function() {
    return {
      hello: function() {
        console.log('hello');
      }
    };
  });

  //myOtherModule.js
  // the empty array correspond to the dependencies (none)
  define([], function() {
    return {
      goodbye: function() {
        console.log('goodbye');
      }
    };
  });

  // otherFile.js
  // here we have `myModule`, `myOtherModule` as dependencies
  define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) {
    console.log(myModule.hello());
    console.log(myOtherModule.goodbye());
  });
// calculator.js
define("calculator", function() {
  return {
    sum: function(a, b) { return a + b; }
  };
});

// app.js
define("app", ["calculator"], function(calculator) {
  console.log(calculator.sum(1, 2)); // => 3
});

The define method can receive three parameters define(id?, dependencies?, factory);. The first is optional, it would be the module id, if it’s not present it would be the one that the loader (in this case RequireJS) is expecting according to his configuration for that given file, for example:

  require.config({
    baseUrl: '...',
    paths: {
        myModule: 'myModule-2.0.0.min',

The second argument is the dependencies, an array of the module (ids) required by the module that is being defined. The dependencies must be resolved prior to the execution of the module factory function, which is the third parameter, and the resolved values will be passed as arguments to this factory function with an order that matches those dependencies.

UMD (Universal Module Definition)

For projects that require you to support both AMD and CommonJS features, there’s yet another format: Universal Module Definition (UMD). UMD essentially creates a way to use either of the two, while also supporting the global variable definition. As a result, UMD modules are capable of working on both client and server.

Here there are some patterns to achieve it https://github.com/umdjs/umd

Example:

  // calculator.js
  (function (name, context, definition) {
    if (typeof module != 'undefined' && module.exports)
      module.exports = definition();
    else if (typeof define == 'function' && define.amd)
      define(name, definition);
    else
      context[name] = definition();
  }('calculator', this, function () {
    // your module here!
    return {
      sum: function(a, b) { return a + b; }
    };
  });

This is an example for the Rmodal library, that supports CommonJS, AMD or globals.

  // https://github.com/zewish/rmodal.js/blob/master/src/rmodal.js

  (function(window, document) {
      "use strict";
    // ...
    if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
        define(function() {
            return RModal;
        });
    }
    else if (typeof module === 'object' && module.exports) {
        module.exports = RModal;
    }
    else {
        window.RModal = RModal;
    }
    })(window, document);    

ES6 Modules

With the new ECMAScript edition, we have native support for a new type of modules. An ES6 module is a file containing JS code. That file is automatically in strict mode, and you can use the keywords import and export inside it.

In that file everything you declare is local to the module, we use export to make something public.

// foo.js

export function foo() {
  console.log("foo")
}

export class Bar {
  // ...
}

// you can export it later with a Export List

function sayHi(){
  //...
}

function sayBye(){
  //...
}

export {sayHi, sayBye};


// otherFile.js

import {sayHi} from "foo.js";
sayHi();

You can also rename both your exports and imports

// foo.js

export { sayHi as hi };

// also several at once:

export {
  sayHi  as hi,
  sayBye as bye
};

// otherFile.js

import { hi } from "foo.js";
import { bye as byebye } from "foo.js";

To play nice with CommonJS and AMD modules, ES6 treats them as if they had a default export:

npm install lodash
import {each, map} from "lodash";
import {default as _} from "lodash";
// shorthand
import _ from "lodash";

// This default export is nothing special, you can define your own
// foo.js

function foo() {
  console.log("foo")
}

export {foo as default};
// short hand
export default foo;

Module objects: you can get the imports as properties from a module namespace object:

// foo.js

function sayHi(){
  //...
}

function sayBye(){
  //...
}

export {sayHi, sayBye};


// otherFile.js

import * as foo from "foo.js";
foo.sayHi();
foo.sayBye();

// this doesn't seem to exist : /
// import { sayHi } as foo from "foo.js";

// There is also a shorthand to import and then re-export:

export { sayHi, sayBye } from "foo.js"
export * from "yetAnotherFile.js"

Some more good notes from * Es6 In-Depth Modules:

  • All exported identifiers must be explicitly exported by name in the source code. You can’t programmatically loop through an array and export a bunch of names in a data-driven way.

  • Module objects are frozen. There is no way to hack a new feature into a module object, polyfill style.

  • There is no error recovery for import errors. An app may have hundreds of modules in it, and if anything fails to load or link, nothing runs. You can’t import in a try/catch block. (The upside here is that because the system is so static, webpack can detect those errors for you at compile time.)

Links: