JavaScript Modules

Published on: 2023-12-30

A group of code and data related to a particular piece of functionality. It encapsulates implementation details, exposes a public API, and is combined with other modules to build a larger application.

JavaScript source code formats

1

ES6 brought built-in modules, but the source code formats that came before them, are still around, too.

MediaRuns onExtension
Scriptbrowsers.js
CommonJS moduleServers.js .cjs
AMD moduleBrowsers.js
ECMAScript moduleBrowsers and Servers.js .mjs

IMPORTANT: Built-in modules where introduced in ES6 (2015), the following code will be written in ES5. Among other things:

Before we had modules, we had scripts

Initially, browsers only had scripts — pieces of code that were executed in global scope.

<script src="other-module1.js"></script>
<script src="other-module2.js"></script>
<script src="my-module.js"></script>

The main file is my-module.js, where we simulate a module:

var myModule = (function () { // OpenIIFE
  // Imports (via global variables)
  var importedFunc1 = otherModule1.importedFunc1;
  var importedFunc2 = otherModule2.importedFunc2;

  // Body
  function internalFunc() {
    // ...
  }
  function exportedFunc() {
    importedFunc1();
    importedFunc2();
    internalFunc();
  }

  // Exports (assigned to global variable `myModule`)
  return {
    exportedFunc: exportedFunc,
  };
})(); // Close IIFE

myModule is a global variable that is assinged the result of immediately invoking a function expression. The function expression starts in the first line. It is invoked in the last line.

This way of wrapping a code fragment is called [[IIFE|IIFE (Immediately Invoked Function Expression)]]. What do we gain from an IIFE? var is not block-scoped (like const and let), it is function-scoped: the only way to create new scopes for a var-declared variables is via functions or methods (with const and let, we can use either functions, methods, or blocks {}). Therefore, the IIFE in the example hides all of the following variables from global scope and minimizes name clashes: importedFunc, importedFunc2, internalFunc, exportedFunc.

Note that the we are using an IIFE in a particular manner: at the end, we pick what we want an export and return it via an object literal. That is called revealing module pattern (coined by Christian Heilmann),

This way of simulating modules, has several issues:

Module systemes created prior to ES6

Prior to ECMAScript 6, JavaScript did not have built-in modules. Therefore, the flexible syntax of the language was used to implement custom module systems within the language. Two popular ones are:

Server side: CommonJS modules

The original CommonJS standard for modules was created for server and desktop platforms. It was the foundation of the original Node.js module system, where it achieved enormous popularity. Contributing to that popularity were the npm package manager for Node and tools that enabled using Node modules on the client side (browserify, webpack, and others).

From now on, CommonJS module means the Node.js version of this standard (which has a few additional features). This is an example of a CommonJS module:

// Imports
var importedFunc1 = require('./other-module1.js').importedFunc1;
var importedFunc2 = require('./other-module2.js').importedFunc2;

// Body
function internalFunc() {
  // ···
}
function exportedFunc() {
  importedFunc1();
  importedFunc2();
  internalFunc();
}

// Exports
module.exports = {
  exportedFunc: exportedFunc,
};

CommonJS can be characterized as follows:

Client side: AMD (Asynchronous Module Definition) modules

The AMD module format was created to be easier to use in browsers than the CommonJS format. Its most popular implementation is RequireJS. The following is an example of an AMD module.

define(['./other-module1.js', './other-module2.js'],
  function (otherModule1, otherModule2) {
    var importedFunc1 = otherModule1.importedFunc1;
    var importedFunc2 = otherModule2.importedFunc2;

    function internalFunc() {
      // ···
    }
    function exportedFunc() {
      importedFunc1();
      importedFunc2();
      internalFunc();
    }
    
    return {
      exportedFunc: exportedFunc,
    };
  });

AMD can be characterized as follows:

On the plus side, AMD modules can be executed directly. In contrast, CommonJS modules must either be compiled before deployment or custom source code must be generated and evaluated dynamically (think eval()). That isn’t always permitted on the web.

Characteristics of JavaScript modules

Looking at CommonJS and AMD, similarities between JavaScript module systems emerge:

ECMAScript modules

ECMAScript modules (ES modules or ESM) were introduced with ES6. They continue the tradition of JavaScript modules and have all of their aforementioned characteristics. Additionally:

ES modules also have new benefits:

This is an example of ES module syntax:

import {importedFunc1} from './other-module1.mjs';
import {importedFunc2} from './other-module2.mjs';

function internalFunc() {
  ···
}

export function exportedFunc() {
  importedFunc1();
  importedFunc2();
  internalFunc();
}

ES6 / ES2015 Modules compatibility

References