Javascript Modules

Module Pattern

Revealing Module Pattern

AMD

commonJS

ES6

Javascript

<script src="/jquery.js"></script>
<script>
    
  var $element = $( 'button' );
  $element.css({
    opacity: 1
  });

 $button.click( function( event ) {
    $element.css({
      opacity: 0
    });
  });

</script>
<script>

  var $element = $( 'header' );
  $element.css({
    background: 'red'
  });

</script>

Pros

Simple to integrate

Still able to split into separate files

Cons

Leaking globals

No encapsulation

No interoperability

Module Pattern

<script src="/jquery.js"></script>
<script>
  
  var module = (function() {
    // Private
    var foo = 'foo';
    
    function privateMethod() {
      ...
    }

    return {
      getFoo: function() {
        return foo;
      },

      publicMethod: function() {
        ...
      }
    };

  })();

  // Error - private methods
  console.log( module.foo );
  module.privateMethod();

  // Accessing public API
  console.log( module.getFoo() );
  module.publicMethod();

</script>
<script src="/jquery.js"></script>
<script>
  
  var module = (function( $ ) {
    // Private
    var $foo = $( '#foo' );
    
    function privateMethod() {
      $foo.css({ background: '#d4d6d7' });
    }

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

  })( jQuery );

  // Error - private method
  console.log( $foo );
  module.privateMethod();

  // Accessing public API
  module.publicMethod();

</script>
<script>
  
  (function( root ) {

    var privateFoo = 'foo';
   
    var module = {

      create: function() {

        return {

          publicMethod: function() {
            module.privateMethod();
            console.log( privateFoo );
          }

        }

      },

      privateMethod: function() {...}

    }

    root.module = module.create();

  })( this );

</script>

Pros

Encapsulation

Ability to 'reveal' an API

Cons

Limits global leak

Patches and changes, i.e. from private to public, can become problematic as the way in which they are referenced changes

Still leaks globals, which can conflict

Data privacy

Late declaration of additional methods (or extensions) to the module wont have access to private members

Unit testing private variables is impossible

Revealing Module Pattern

<script src="/jquery.js"></script>
<script>

  var module = (function() {

    var foo = 'foo';

    function bar() {...};

    // Public API
    return {
      variable: foo,
      publicMethod: bar
    };

  })();

  
  console.log( module.variable );
  module.publicMethod();

</script>

Pros

Syntactically more consistent than standard module pattern

Clear and concise public API declaration

Cons

A late patch to the module object will fail if functions reference each other as any patch will only be applied to the publicly exposed functions, not those used by the module directly.

In short, modules become slightly more fragile.

Asynchronous Module Definition

// script.js
!function() {

  define( 'myAwesomeModule', 
    [
      'jQuery',
      'lodash'
    ],

    function( $, _ ) {

      return {
        foo: 'foo',
        bar: function() { ... }
      };

    }
  );


  require( [ 'myAwesomeModule' ], function( module ) {
    module.bar();
    console.log( module.foo );
  });

}();

Pros

Defines a module structure

Dependency management

Cons

Proper encapsulation and preservation of global, thus eliminating naming conflicts

Highly flexible

Encourages asynchronous loading and allows lazy loading of scripts

Fair amount of boilerplate

Its flexibility means it can be confusing or overly verbose

Is asynchronous script loading really as useful as you might think?

CommonJS

// script.js

var $ = require( 'jQuery' );
var _ = require( 'lodash' );


module.exports = {

  foo: 'foo',
  bar: function() {...}

};

Pros

Many similar pros to AMD, such as encapsulation and clear definition

Super simple

Cons

Simplicity

Requires a build step to make sense in browser-land without destroying global

ES6 Harmony Modules

// module.js

var privateFoo = 'foo';

export default class Superman extends Man {
  constructor( name ) {
    this.name = name;
  }

  get name() {
    return this.name;
  }
  
  say() {
    super();
    console.log( 'Hello from', this.name );
  }
}


// app.js
import Superman from 'module';

var clark = new Superman( 'Clark' );

Pros

Proper module system

Will likely be available to use as part of a <module> tag

Cons

Simple CommonJS format

Fairly gnarly build steps involved

Most libraries currently dont support ES6 modules, however, there are build step work arounds which will attempt to work out the type of module being imported and load it in a sensible manner. This allows ES6, CommonJS and AMD to be used alongside each other.

Javascript Modules

By Matt Styles