CommonJS, AMD, ES Modules

CommonJS, AMD, ES Modules

Hello, Habre!

The history of JavaScript’s modularity began with chaos: global variables, name conflicts, and complexities with dependencies. Over time, the community has proposed several approaches to organizing modules, ranging from CommonJSwhich formed the basis of Node.js, to AMDbetter for asynchronous loading of code in browsers And approaching the present time appeared ES Modules standardized and built into the module engine language, which became part of ECMAScript in 2015.

In this article, we will briefly consider CommonJS, AMD, and finally – how ES Modules appeared.

CommonJS

CommonJS aimed to create a standard for modules that could be used in any environment, including server applications. The main mission was to to facilitate the development of modular code, which would be structured and easy to use. CommonJS defines a module as a nested block of code, which interacts with other modules through the export and import of values.

In Node.js, each file is considered a module, and it has adopted the CommonJS specification as the de facto standard for organizing modules.

CommonJS module system in Node.js supports both synchronous loading of modules, so and so lazy loading. Node.js uses caching of loaded modules, which reduces the time of their reloading and, as a result, speeds up the execution of the program.

In CommonJS, each JavaScript file is considered a separate module. If you need functions, objects, or primitives available outside the current module, you can usemodule.exports:

// myModule.js
const myFunction = () => {
  console.log("Привет из myFunction");
}

const myVariable = 123;

module.exports = { myFunction, myVariable };

Here we export the object containing the function myFunction and variable myVariable.

There is a function in another CommonJS module to use the exported values require(). This function takes one argument, the path to the module to be imported, and returns the object exported by the target module:

// anotherModule.js
const { myFunction, myVariable } = require('./myModule');

myFunction(); // "Привет из myFunction"
console.log(myVariable); // 123

We import functionality from myModule.js in anotherModule.jsusing object destructuring to access the myFunction and myVariable.

In Node.js exports is short for module.exports. From the very beginning exports and module.exports refer to the same object. Nevertheless, if prescribed module.exports to the new object, it will not affect the exports. They usually use it module.exports for export to avoid confusion:

// myModule.js
exports.myFunction = () => {
  console.log("Экспортируется через exports");
}

The code is similar to the previous example with module.exportsbut here we add myFunction directly to exports.

Same function require() is used to import built-in Node.js modules such as fs to work with the file system, as well as to import third-party libraries installed via NPM, such as fs:

const fs = require('fs');

fs.readFile('path/to/file', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

AMD

AMD is a standard that allows you to define JavaScript modules and their dependencies in asynchronous style. Scripts are usually loaded synchronously, which can cause a noticeable delay in page interactivity if the script is on a remote server. AMD is generally built to solve this problem.

Among the various AMD implementations, the most suitable is RequireJS. It is the de facto standard for developers who want to implement asynchronous loading of modules in their projects.

RequireJS provides a simple API for asynchronously loading JavaScript files and managing dependencies. With its help, you can define modules, specify their dependencies and load them only when you really need them.

Each module is defined by a function define()takes a list of dependencies and a factory function. A simple module with no dependencies:

// hello.js
define(function() {
    return function hello() {
        console.log('Привет, мир!');
    };
});

Module hello.js can be downloaded and used as follows:

require(['hello'], function(hello) {
    hello(); // "Привет, мир!"
});

Suppose there is a module math.jswhich provides addition and multiplication functions:

// math.js
define(function() {
    return {
        add: function(a, b) {
            return a + b;
        },
        multiply: function(a, b) {
            return a * b;
        }
    };
});

And you need to use these functions in another module calculator.js:

// Файл: calculator.js
define(['math'], function(math) {
    console.log(math.add(1, 2)); // Выводит: 3
    console.log(math.multiply(3, 4)); // Выводит: 12
});

To connect RequireJS and start loading modules from an HTML file, add data-main attribute in the tag <script>specifying the entry point of the program:

<!-- index.html -->
<script data-main="scripts/main" src="https://habr.com/ru/companies/otus/articles/798455/scripts/require.js"></script>

In the file scripts/main.js can be used require() to download modules:

// scripts/main.js
require(['calculator'], function() {
    // модуль calculator и его зависимости загружены и выполнены
});

RequireJS supports plugins for loading not only JavaScript modules, but also other types of resources, such as a plugin usage example text to download a text file:

// загрузка текстового содержимого файла mydata.txt
define(['text!../data/mydata.txt'], function(data) {
    console.log(data); // выводит содержимое mydata.txt
});

To use the plugin textit must be connected first.

ES Modules

ES Modules represent one of the most significant additions to the ECMAScript 2015 standard. This standard changed the JS modularity approach by offering native support for modules in the language. JS developers have long used various ways to organize and modularize their code, such as CommonJS and AMD. Before ES6, however, none of these methods were part of JavaScript itself.

ES Modules has statistical structure, which allows js-engines to analyze module dependencies at the stage of code analysis, even before its execution. This differs from CommonJS, where modules and their dependencies are defined and loaded at runtime.

ES Modules, their support is implemented in all modern browsers, so you can use modules without the need to use tools like Webpack or Babel for code transpilation.

The main task of modules is to export parts of the code so that they can be used in other files. Named export:

// экспорт отдельных функций
export function myFunction() { ... }
export const myConstant = 123;

// экспорт списка
const someConstant = 456;
function someFunction() { ... }
export { someFunction, someConstant };

Named exports allow you to export multiple values ​​that can be imported by their names.

Default export:

// экспорт функции по умолчанию
export default function() { ... }

// экспорт класса по умолчанию
export default class MyClass { ... }

Each module can have only one default export.

There is an operator to use ES Modules exported values import.

import { myFunction, myConstant } from "https://habr.com/ru/companies/otus/articles/798455/./myModule.js";х

You can import only the required parts of the module by specifying their names in curly brackets.

Import with renaming:

import { myFunction as functionOne, myConstant as constantOne } from "https://habr.com/ru/companies/otus/articles/798455/./myModule.js";

Import all named object exports:

import * as myModule from "https://habr.com/ru/companies/otus/articles/798455/./myModule.js";

Such an import collects all named exports of the module into one object.

Default export import:

import myDefault from "https://habr.com/ru/companies/otus/articles/798455/./myModule.js";

There is also dynamic import using a function import()which returns a Promise:

import("https://habr.com/ru/companies/otus/articles/798455/./myModule.js").then((module) => {
    // использование модуля
});

Can be used <link rel="modulepreload" href="https://habr.com/ru/companies/otus/articles/798455/./myModule.js">, specifying the path to the module. This tells the browser to pre-load the module before it is actually requested in the code.


In conclusion, let’s make a comparison table for the three main JavaScript module systems: CommonJS, AMD and ES Modules:

Sign

CommonJS

AMD

ES Modules

Synchronism

Synchronous

Asynchronous

Asynchronous*

Main use

Node.js

browsers

browsers and Node.js

Syntax

require/module.exports

define and require

import/export

Loading modules

During execution

During execution

Static analysis

Live links

No

No

So

Dynamic loading

Limited

So

So (import())

Caching modules

So

So

So

Prevalence

Popular with Node.js

It is used in specific cases

The ECMAScript standard

*ES Modules can be loaded asynchronously in browsers, but also support static dependency analysis at compile time.

In the end, ES Modules, gradually becoming the de facto standard for JavaScript modularity, offers the most harmonious and solution, but knowing all three systems makes us better and more competent in this topic.

Finally, I want to invite you to a free webinar on prototypical inheritance in JavaScript. Register, it will be interesting.

Related posts