[JS] CommonJS vs. ES Modules: Synchronous and Asynchronous Module Loading
Tags: javascript, webdev, programming, node
Published: 2024-01-15 03:25:40

JavaScript employs two main module systems: CommonJS using `require()` and `module.exports` for imports and exports, and ECMAScript Modules (ESM), defined in the ECMAScript standard, utilizing `import` and `export`. CommonJS features synchronous module loading, while ESM brings asynchronous loading. I will explore and compare each module loading process.

## CommonJS Example

In the CommonJS, module loading is synchronous. Consider the following code structure:

cjs.js:

```javascript
console.log("cjs start");

require("./test1");
require("./test2");
require("./test3");

console.log("cjs end");
```

test1.js:

```javascript
console.log("test1");
```

test2.js:

```javascript
console.log("test2");
```

test3.js:

```javascript
console.log("test3");
```

Execute the following command to observe synchronous module loading in action:

```bash
NODE_DEBUG=module node cjs.js
```

The output would be:

![CommonJS DEBUG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nya83bl073bfli7ctj4s.png)


Let us break down the code and understand the flow:

1. `cjs.js`: This is the main script that is executed. It starts by logging `cjs start.`
2. Next, the contents of the `test1.js` is loaded and the JavaScript code is evaluated and executed. Then, similarly `test2.js` and `test3.js` are executed.
3. While loading the modules synchronously, each module (`test1.js`, `test2.js`, and `test3.js`) logs its respective message.
4. After all required modules are loaded and executed, the main script continues to execute and logs `cjs end`.

Unlike imports in ESM, require in CommonJS is synchronous and blocks the execution of the main script until the required modules are loaded.

## ES Modules Example

In the ES Modules, the loading paradigm shifts to asynchronous. Consider the following code structure:

esm.js:

```javascript
console.log("esm start");

import("./test1.js");
import("./test2.js");
import("./test3.js");

console.log("esm end");
```

The content of `test*` files need not be changed. Execute the following command to confirm asynchronous module loading:

```bash
NODE_DEBUG=esm node esm.js
```

The output would be:

![ES Modules DEBUG](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/977br6bjzb4plfqdasqx.png)



Let us break down the code and understand the flow:

1. `esm.js` is the main script that is executed. It starts by logging `esm start`.
2. Next, it uses dynamic imports to import and execute `test1.js`, `test2.js`, and `test3.js`. Dynamic imports are asynchronous, so they do not block the execution of the main script.
3. While the dynamic imports are in progress, `esm.js` continues to execute and logs `esm end`.
4. As the dynamic imports complete, each imported module (`test1.js`, `test2.js`, and `test3.js`) logs its respective message.

Therefore, the expected output reflects the asynchronous nature of dynamic imports, and you will see `esm start` and `esm end` logged first, followed by the logs from `test1.js`, `test2.js`, and `test3.js`.

CommonJS employs synchronous module loading, meaning that until one module is fully loaded, the loading of other modules is restricted. On the other hand, ESM Modules utilize asynchronous loading, enabling the concurrent loading of multiple modules. This characteristic is particularly advantageous as the number of modules to be loaded increases, potentially leading to improved performance.

That is about it. Happy coding!