An introduction to module systems in JavaScript

The concept of modules comes from the modular programming paradigm. This paradigm proposes that software be composed of separate and interchangeable components called “modules” by breaking down program functions into stand-alone files that can operate separately or be coupled into an application.


A module is a self-contained file that encapsulates code to implement certain functionality and promote reuse and organization.

Here you will cover the module systems used in JavaScript applications, including the module model, the CommonJS module system used in most Node.js applications, and the ES6 module system.


The module model

Before the introduction of native JavaScript modules, the module design pattern was used as a system of modules to extend variables and functions to a single file.

This was implemented using immediately called function expressions, commonly referred to as IIFE. An IIFE is a non-reusable function that runs as soon as it is created.

Here is the basic structure of an IIFE:

(function () {
})();

(() => {
})();

(async () => {
})();

The code block above describes IIFEs used in three different contexts.

IIFEs were used because variables declared inside a function are scoped to the function, making them accessible only inside the function, and because functions allow you to return data ( making them accessible to the public).

For instance:

const foo = (function () {
const sayName = (name) => {
console.log(`Hey, my name is ${name}`);
};
return {
callSayName: (name) => sayName(name),
};
})();
foo.callSayName("Bar");

The code block above is an example of how modules were created before the introduction of native JavaScript modules.

The code block above contains an IIFE. The IIFE contains a function that it makes accessible by returning it. All variables declared in the IIFE are protected from global scope. Thus, the method (sayName) is accessible only by the public service, callSayName.

Note that the IIFE is saved in a variable, mad. Indeed, without a variable pointing to its location in memory, the variables will be inaccessible after the execution of the script. This pattern is possible thanks to JavaScript closures.

The CommonJS module system

The CommonJS module system is a module format defined by the CommonJS group to solve JavaScript scoping issues by running each module in its namespace.

The CommonJS module system works by forcing modules to explicitly export variables they wish to expose to other modules.

This module system was created for server-side JavaScript (Node.js) and as such is not supported by default in browsers.

To implement CommonJS modules in your project, you must first initialize NPM in your application by running:

npm init -y

Variables exported following the CommonJS module system can be imported as follows:


const installedImport = require("package-name");
const localImport = require("/path-to-module");

Modules are imported into CommonJS using the require statement, which reads a JavaScript file, executes the read file, and returns the exports object. The exports The object contains all the exports available in the module.

You can export a variable following the CommonJS module system using either named exports or default exports.

Named exports

Named exports are exports identified by the names assigned to them. Named exports allow multiple exports per module, while default exports do not.

For instance:


exports.myExport = function () {
console.log("This is an example of a named export");
};
exports.anotherExport = function () {
console.log("This is another example of a named export");
};

In the code block above, you export two functions named (myexport and anotherExport) by attaching them to the exports object.

Similarly, you can export functions as follows:

const myExport = function () {
console.log("This is an example of a named export");
};
const anotherExport = function () {
console.log("This is another example of a named export");
};
module.exports = {
myExport,
anotherExport,
};

In the code block above, you set the exports object to named functions. You can only assign exports object to a new object via the module object.

Your code would return an error if you tried to do it this way:


exports = {
myExport,
anotherExport,
};

There are two ways to import named exports:

1. Import all exports as a single object and access them separately using dot notation.

For instance:


const foo = require("./main");
foo.myExport();
foo.anotherExport();

2. Destructuring the exports of exports object.

For instance:


const { myExport, anotherExport } = require("./main");
myExport();
anotherExport();

One thing is common to all import methods, they must be imported using the same names they were exported with.

Default exports

A default export is an export identified by a name of your choice. You can only have one default export per module.

For instance:


class Foo {
bar() {
console.log("This is an example of a default export");
}
}
module.exports = Foo;

In the code block above, you are exporting a class (fooo) by reassigning the exports oppose it.

Importing default exports is similar to importing named exports, except you can use any name you want to import them.

For instance:


const Bar = require("./main");
const object = new Bar();
object.bar();

In the code block above, the default export was named Baralthough you can use any name you like.

The ES6 module system

The ECMAScript Harmony Module System, commonly referred to as ES6 Modules, is the official JavaScript Module System.

ES6 modules are supported by browsers and servers, although you will need a bit of configuration before using them.

In browsers you need to specify the type as module in the script import tag.

Thereby:


<script src="./app.js" type="module"></script>

In Node.js you need to set type at module in your package.json case.

Thereby:


"type":"module"

You can also export variables using the ES6 module system using named exports or default exports.

Named exports

Similar to named imports in CommonJS modules, they are identified by the names assigned to them and allow multiple exports per module.

For instance:


export const myExport = function () {
console.log("This is an example of a named export");
};
export const anotherExport = function () {
console.log("This is another example of a named export");
};

In the ES6 module system, named exports are exported by prefixing the variable with the export keyword.

Named exports can be imported into another module in ES6 the same way as CommonJS:

  • Deconstruct the required exports from the exports object.
  • Import all exports as a single object and access them separately using dot notation.

Here is an example of destructuring:


import { myExport, anotherExport } from "./main.js";
myExport()
anotherExport()

Here is an example of importing the entire object:

import * as foo from './main.js'
foo.myExport()
foo.anotherExport()

In the code block above, the asterisk (*) means “all”. The as keyword assigns the exports object to the string that follows it, in this case, mad.

Default exports

Similar to default exports in CommonJS, they are identified by any name you choose and you can only have one default export per module.

For instance:


class Foo {
bar() {
console.log("This is an example of a default export");
}
}
export default Foo;

Default exports are created by adding the default keyword after export keyword, followed by the name of the export.

Importing default exports is similar to importing named exports, except you can use any name you want to import them.

For instance:


import Bar from "./main.js";

Mixed exports

The ES6 module standard allows you to have both default exports and named exports in a module, unlike CommonJS.

For instance:


export const myExport = function () {
console.log("This is another example of a named export");
};
class Foo {
bar() {
console.log("This is an example of a default export");
}
}
export default Foo;

Importance of modules

Dividing your code into modules not only makes them easier to read, but also more reusable and maintainable. Modules in JavaScript also make your code less error-prone because all modules run in strict mode by default.

Comments are closed.