Modules

– from the past to now –

Michael Kühnel

  • Doing stuff for the Internet since Netscape 4.7
  • Frontend Developer at Micromata
  • Open Source Fan
  • Yeoman Core Team Member
  • Proud Member of the bullgit Family

What is a module?

A module is a reusable piece of code that encapsulates implementation details and exposes public methods so it can be used by other code.

Why do we need modules?

In short: Do give code structure and increase maintainibility

In JavaScript, modules should ideally allow us to:

  • abstract code: to delegate functionality
  • encapsulate code: to keep files short
  • avoid duplication: to allow us to keep things DRY 
  • reuse code: to avoid writing the same code over
    and over again
  • manage dependencies: to easily change dependencies without rewriting our code

ES5 module patterns

Revealing Module pattern

// modulName.js

var modulName = (function() {

  var privateVar = 'Ben Cherry';
  var publicVar = 'Hey there!';

  function _privateMethod() {
    console.log('Name:' + privateVar);
  }

  function publicSetName(strName) {
    privateVar = strName;
  }

  function publicGetName() {
    privateMethod();
  }


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

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

})();


// otherFile.js

modulName.setName('Paul Kinlan');
// modulName.js

var appName = window.appName || {};

appName.modulName = (function($) {
  'use strict';

  var yourPublicMethod = function(message) {
    console.info(message);
  };

  var _yourPrivateMethod = function(message) {
    console.info(message);
  };


  $(function() {
    _yourPrivateMethod('Hi Private.');
  });

  // Return functions to make them accessible from outside.
  return {
    yourPublicMethod: yourPublicMethod
  };

})(jQuery);


// otherFile.js

$(function() {
  'use strict';

  appName.modulName.yourPublicMethod('Hi public.');
});

Module systems

Before ES6, JavaScript did not have an official syntax to define modules. Therefore, smart developers came up with various formats to define modules in JavaScript.

Modul sytems describe the syntax we can use to define and import modules

  • Asynchronous Module Definition (AMD)
  • CommonJS
  • Universal Module Definition (UMD)
  • ES6 module format

Some of the most widely adapted and well known formats are:

Asynchronous Module Definition (AMD)

The AMD format is used in browsers and uses a define function to define modules

// Filename: foo.js

// Calling define with a dependency array and a factory function
define(['jquery'], function($) {
  
  // Methods
  function myFunc() {};

  // Exposed public methods
  return myFunc;
});

Obsolete

You will only find this in legacy projects

Was used with require.js in the browser or transpiled with r.js

CommonJS 

The CommonJS format is used in Node.js and uses require and module.exports to define dependencies and modules

// Filename: foo.js

// Dependencies
var $ = require('jquery');

//  Methods
function myPrivateMethod(){};
function myPublicMethod(){};

// Expose public method (single)
module.exports = myPublicMethod;
// Filename: foo.js

// Dependencies
var $ = require('jquery');
var _ = require('underscore');

// Methods
function a() {}; // Private because it's omitted from module.exports
function b() {}; // Public because it's defined in module.exports
function c() {}; // Public because it's defined in module.exports

// Exposed public methods
module.exports = {
  b: b,
  c: c
};

Still used and very common

Default module system in Node.js

Can be used in the browser with module bundlers like webpack and browserify.  

Universal Module Definition (UMD)

UMD modules can be used both in the browser and in Node.js (and can be consumed by module bundlers)

(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define(['jquery'], factory);
  } else if (typeof exports === 'object') {
    // Node, CommonJS-like
    module.exports = factory(require('jquery'));
  } else {
    // Browser globals (root is window)
    root.returnExports = factory(root.jQuery);
  }
}(this, function($) {
  // Methods
  function myFunc() {};

  // Exposed public method
  return myFunc;
}));
(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 because it's not returned
  function b() {}; // Public because it's returned
  function c() {}; // Public because it's returned

  // Exposed public methods
  return {
    b: b,
    c: c
  }
}));

Still used and pretty common

Preferred format for publishing libraries

Can be used everywhere. Creation of UMD modules can be automated with module bundlers rollup.  

ES6 module format

As of ES6, JavaScript has a native module format which can be used in modern browsers and can be consumed by module bundlers (and will be useable in Node.js in the future)

// lib.js

// Export the function
export function publicFunction() {
  console.log('Hello');
}

// Do not export the function
function somePrivateFunction() {
  // ...
}

It uses an export token to export a modules public API:

and an import token to import parts that a module exports:

// otherFile.js

import { sayHello } from './lib';

sayHello();     // => Hello
// otherFile.js

import { sayHello as say } from './lib';

say();  // => Hello

We can even give imports an alias using as:

or load an entire module at once:

// otherFile.js

import * as lib from './lib';

lib.sayHello();  // => Hello
// lib.js

// Export default function
export default function sayHello(){  
  console.log('Hello');
}

// Export non-default function
export function sayGoodbye(){  
  console.log('Goodbye');
}

The format also supports default exports:

which you can import like this:

// otherFile.js

import sayHello, { sayGoodbye } from './lib';

sayHello();  // => Hello

sayGoodbye();  // => Goodbye
// lib.js

// Export default function
export default function sayHello(){  
  console.log('Hello');
}

// Export non-default function
export function sayGoodbye(){  
  console.log('Goodbye');
}

// Export simple value
export const apiUrl = '...';

// Export object
export const settings = {  
  debug: true
}

You can export not only functions, but anything you like:

The way to go

You should use ES6 modules if your a about to start a new project with clientside JavaScript

With the lack of native browser support you should combine Babel with a module loader like System.js or with module bundler like webpack or browserify.

Additional reading material

Questions? 

Thanks 😊