-->
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.
ES6 brought built-in modules, but the source code formats that came before them, are still around, too.
Media | Runs on | Extension |
---|---|---|
Script | browsers | .js |
CommonJS module | Servers | .js .cjs |
AMD module | Browsers | .js |
ECMAScript module | Browsers and Servers | .js .mjs |
IMPORTANT: Built-in modules where introduced in ES6 (2015), the following code will be written in ES5. Among other things:
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:
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:
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:
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.
Looking at CommonJS and AMD, similarities between JavaScript module systems emerge:
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();
}