Learn what JavaScript modules are: what is CommonJS, AMD, System.js, require.js, ES2015, ECMAScript6 and Webpack.
As JavaScript becomes more commonly used, namespaces (namespaces where our application's identifiers coexist) and dependencies become more difficult to manage.
Considering that before the advent of ES6, Javascript did not natively support the use of modules, programmers managed to develop their own module systems, leveraging features of the same language.
Today we will see which alternatives are the most used, and the difference between them.
Before you start: Why are Javascript modules necessary??
If you've developed for other platforms, you're likely to have a sense of the concepts of Encapsulation y Dependency.
Years ago, the vast majority of applications were developed in isolation.
Today, it's quite the opposite.
It is common that some of the requirements of a system that is being developed can be implemented using an existing solution as a base.
The instant an existing component is introduced into a new project, a dependency is created between this project and the component used.
Since these pieces need to work together, It is important that there are no conflicts among them.
So, if we don't perform any kind of encapsulation, it's a matter of time before 2 modules conflict.
This is one of the reasons why C libraries use a prefix in their components.
Encapsulation is essential to prevent conflicts and facilitate development.
When it comes to dependencies, in client-side JavaScript development, they have traditionally been dealt with implicitly.
In other words, it has always been the task of the developer:
- Ensure that dependencies are satisfied when each block of code is ejected.
- Also, ensure that these dependencies are loaded in the correct order.
As we write more Javascript code in our applications, dependency management becomes more cumbersome.
Questions arise such as:
¿Where we should put the new dependencies in order to maintain proper order?
Module systems (module systems) alleviate this problem and others.
They are born from the need to "accommodate" the growing JavaScript ecosystem.
Let's see what the different solutions bring to the table.
A First Solution: The Revealing Module Pattern
Before the arrival of module systems:
A particular programming pattern began to be used more and more frequently in JavaScript: the revealing module pattern or "the pattern of the revealing module".
var miModuloRevelador = (function () {
var nombre = "Juan Ramos",
saludo = "Hola !";
// Función privada
function imprimirNombre() {
console.log("Nombre:" + nombre);
}
// Función pública
function asignarNombre(nuevoNombre) {
nombre = nuevoNombre;
}
// Revelar accesos públicos (opcionalmente con otros nombres)
return {
setName: asignarNombre,
greeting: saludo
};
})();
miModuloRevelador.setName("Carlos");
Scopes in Javascript have always worked at the function level (until before the advent of let in ES2015).
This means that everything that is declared within a function cannot escape its scope. It is for this reason that the pattern revealing module relies on functions to encapsulate private content (like many other Javascript patterns).
In the example above, public functions and variables are exposed in the returned object (at the end with a return).
All other statements are protected by the scope of the function that contains them.
You should note that the variable is not receiving the function directly, but rather the result of executing the function, i.e. the object that is returned through the return of the anonymous function.
This is known as "Immediately-invoked function expression". If you have been using Javascript for a short time and find it confusing, I recommend that before continuing you read this article on functions that are invoked immediately after their creation.
PROS
- Simple enough to be used anywhere (no libraries or other additional support required).
- Multiple modules can be defined in a single file.
CONS
- There is no way to import modules on a scheduled basis (except using
eval). - Dependencies must be managed manually.
- Asynchronous module loading is not possible.
- Circular dependencies can be problematic.
CommonJS
CommonJS is a project that defines a set of specifications for the Javascript ecosystem, outside the browser (e.g., server-side or for desktop applications).
One of the areas that the CommonJS team is trying to address is Javascript modules.
The Node.js developers originally tried to follow the CommonJS specification, but later changed their decision.
In terms of modules, the implementation in Node.js was influenced:
// En circle.js
const PI = Math.PI;
exports.area = (r) => PI * r * r;
exports.circumference = (r) => 2 * PI * r;
// En otro archivo
const circle = require('./circle.js');
console.log('El área de 1 círculo de radio 4 es: ' + circle.area(4));
There are abstractions about the Node.js module system, in the form of libraries, that act as a bridge between the Node.js modules and CommonJS. In this article we only look at the basic features.
In both Node and CommonJS, there are 2 essential words for interacting with modules: require y exports.
-
requireIt is a function that can be used to import symbols from another module into the current scope. The parameter passed torequireis the module ID. In the Node implementation, it is the name of the module within thenode_modules(or, in any case, the route to its location). -
exportsis a special object: everything that is put in it can be exported as a public element (keeping the name of the elements).
The modules in CommonJS were designed with server-side development in mind. Naturally, API is synchronous. That is, modules are loaded at the time and in the order they are required within a source code file.
PROS
- It's simple. A developer can understand the concept without seeing the documentation.
- Enables dependency management: Modules require other modules, and are loaded in the requested order.
requireCan be used anywhere: modules can be loaded programmatically.- Supports circular dependencies.
CONS
- Its synchronous API makes it unsuitable to use for certain cases (client-side).
- One file per module.
- Browsers require a library to interpret it.
- There is not 1 build function for modules (although Node supports it).
Implementations
We have already talked about a partial implementation: Node.js
On the client side there are 2 popular options: webpack and browserify.
Asynchronous Module Definition (AMD)
AMD was born from a group of developers who were unhappy with the direction taken by CommonJS. The main difference between AMD and CommonJS lies in their support for asynchronous module loading.
// Llamamos a define y le pasamos 1 arreglo de dependencias y 1 función que fabrica al módulo
define(['dependencia1', 'dependencia2'], function (dep1, dep2) {
// Devolvemos una definición del módulo
return function () {};
});
// Equivalente a:
define(function (require) {
var dependencia1 = require('dependencia1'),
dependencia2 = require('dependencia2');
return function () {};
});
Asynchronous loading in JS is possible using closures: A function is called when the required modules finish loading.
The definition and import of modules is carried out by the same function: when a module is defined, its dependencies are explicitly indicated.
In this way, an AMD loader can have a complete picture of the dependency graph for a given project at runtime.
Libraries that do not depend on others can be loaded at the same time. This is very important for browsers, where initial load time is an essential point to provide a good user experience.
PROS
- Asynchronous loading (better startup times).
- Supports circular dependencies.
- It is compatible with
requireyexports. - Fully integrated dependency management.
- Modules can be separated into multiple files if needed.
- Supports building functions.
- Supports plugins (to customise loading steps).
CONS
- Syntactically it is a little more complex.
- Requires loading libraries, or a transpilation process.
Implementations
AMD's best-known implementations are require.js and Dojo.
Using require.js is relatively straightforward. Just include the library in our HTML and use the data-main to indicate which module should load first. Dojo has a similar setup.
Modules in ES2015
Fortunately, the ECMA team (in charge of Javascript standardisation) decided to address the issue of modules.
The result can be seen in the latest version of the Javascript standard: ECMAScript 2015 (formerly known as ECMAScript 6). The result is syntactically pleasing, and compatible with both modes of operation (synchronous and asynchronous).).
//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5
The directive import It allows you to bring modules into the current scope. This directive, in contrast to require y define it is non-dynamic (i.e. it cannot be called anywhere). The directive export, On the other hand, it can be used to explicitly make the elements public.
The static nature of import y export allows static analysers to build a complete tree of dependencies without executing code.
PROS
- Supports synchronous and asynchronous loading.
- It is syntactically simple.
- It is integrated into the language itself (eventually it will be supported everywhere without the need for libraries).
- Supports circular dependencies.
CONS
- Still not supported everywhere.
Implementations
Unfortunately, not all JS interpreters support ES2015 in their stable versions.
However, there are "transpilators" (transpilers) that add this support.
An example is the ES2015 preset for Babel. Babel is a transpiler, y ES2015 preset is a plugin that allows you to transform ES2015 (ES6) code into ES5 (the typical version of Javascript supported by all browsers for several years).
One universal charger: System.js
¿You want your project to work properly for all cases?
System.js is a universal module loader, supporting CommonJS, AMD and ES modules2015.
A better alternative
Today, Webpack offers the same as System.JS and much more.
Webpack is a module packer that also optimises our files for production, minifying and joining them as required (in fact it allows you to use loaders to perform more tasks during this process).
Using SystemJS and achieving the same as Webpack would imply additionally using Gulp, or "SystemJS builder" to package our project for production.
Conclusion
Javascript module systems arise as a need for the programmers themselves to encapsulate different functionalities in reusable "blocks of code". These blocks are called modules, and it's important to have a mechanism in place to manage the dependencies between these modules.
This is how specifications arise, which seek to define a format for the import and export of modules, such as CommonJS and AMD.
These specifications have their corresponding implementations with slight differences.
In order to bring some order to so much chaos, a new version of the Javascript standard appears: ES2015 (formerly known as ES6).
Great. So why all the fuss?
The thing is that not all browsers have finished implementing this standard in a stable way, and a large number of users use old versions.
- The solution then is to "transform our code" into code that all browsers can understand, making use of
transpilers. - Or use
polyfillsto give browsers the ability to understand features they haven't yet implemented.
There are many alternatives, such as Webpack, and more recently Vite.
This is because they not only solve this problem.
They also optimise and package our code, making it production-ready.
But Javascript is not only on the client side.
The specifications also apply to Javascript on the server side.
That's why in this article we have mentioned NodeJS.
Call to action
The Javascript ecosystem changes very often.
This is largely because the most prominent tech companies:
- Facebook,
- Google,
- X (Twitter),
- Instagram.
They are always on the lookout for better tools.
They stop using one to adopt a better one, or create their own proposed version.
But there is no need to fear these changes. In the end, there are many ways to do "almost" the same thing.
If you want to learn more, I invite you to follow my JavaScript course.
If you found it interesting, please help me share this article.



