JavaScript modules

1. CommonJS

  • Implemented by Node.js
  • Used for the server side when you have modules installed.
  • No runtime/async module loading.
  • Import via “require”.
  • Export via “module.exports”.
  • When you import you get back an object.
  • No tree shaking, because when you import you get an object.
  • No static analyzing, as you get an object, so property lookup is at runtime.
  • You always get a copy of an object, so no live changes in the module itself.
  • Poor cyclic dependency management.
  • Simple syntax
// log.js
function log() {
  console.log('Example of CJS module system');
}

// Expose log to other modules
module.exports = { log }
// index.js
var logModule = require('./log')

logModule.log()

2. AMD: Async Module Definition

= Implemented by RequireJs.

  • Used for the client side (browser) when you want dynamic loading of modules.
  • Import via “require”.
  • Complex syntax.
// log.js
define(['logModule'], function() {
  // Export (expose) foo for other modules
  return {
    log: function() {
      console.log('Example of AMD module system')
    }
  }
})
// index.js
require(['log'], function (logModule) {
  logModule.log()
})

3. UMD: Universal Module Definition

  • Combination of CommonJS + AMD (that is, syntax of CommonJS + async loading of AMD).
  • Can be used for both AMD/CommonJS environments
  • UMD essentially creates a way to use either of the two, while also supporting the global variable definition. As a result, modules are capable of working on both client and server.
// log.js
(function (global, factory) {
  if (typeof define === 'function' && define.amd) {
    define('exports'], factory)
  } else if (typeof exports !== 'undefined') {
    factory(exports)
  } else {
    var mod = {
      exports: {}
    }
    factory(mod.exports)
    global.log = mod.exports
  }
})(this, function (exports) {
  'use strict'

  function log() {
    console.log('Example of UMD module system')
  }
  // expose log to other modules
  exports.log = log
})

4. ES6 (ES2015)

  • Used for both server/client side.
  • Runtime/static loading of modules supported.
  • When you import, you get back bindings value (actual value).
  • Import via “import” and export via “export”.
  • Static analyzing - you can determine imports and exports at compile time (statically) — you only have to look at the source code, you don’t have to execute it.
  • Tree shakeable, because of static analyzing supported by ES6.
  • Always get an actual value so live changes in the module itself.
  • Better cyclic dependency management than CommonJS.
// log.js
const log = () => {
  console.log('Example of ES module system');
}
export default log
// index.js
import log from './log'

log()

This is all about different types of JS module systems and how they have evolved.