CommonJS vs. ES modules in Node.js
Tags: node, webdev, javascript, discuss
Published: 2022-01-11 20:31:16

**Written by [Kingsley Ubah](https://blog.logrocket.com/author/kingsleyubah/)** ✏️

In modern software development, modules organize software code into self-contained chunks that together make up a larger, more complex application.

In the browser JavaScript ecosystem, the use of JavaScript modules depends on the `import` and `export` statements; these statements load and export EMCAScript modules (or ES modules), respectively.

The ES module format is the official standard format to package JavaScript code for reuse and most modern web browsers natively support the modules.

Node.js, however, supports the CommonJS module format by default. CommonJS modules load using `require()`, and variables and functions export from a CommonJS module with `module.exports`.

The ES module format was introduced in [Node.js v8.5.0](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md#8.5.0) as the JavaScript module system was standardized. Being an experimental module, the `--experimental-modules` flag was required to successfully run an ES module in a Node.js environment.

However, starting with version 13.2.0, Node.js has stable support of ES modules.

This article won’t to cover much on the usage of both module formats, but rather how CommonJS compares to ES modules and why you might want to use one over the other.

## Comparing CommonJS modules and ES modules syntax

By default, Node.js treats JavaScript code as CommonJS modules. Because of this, CommonJS modules are characterized by the `require()` statement for module imports and `module.exports` for module exports.

For example, this is a CommonJS module that exports two functions:

```js
module.exports.add = function(a, b) {
        return a + b;
} 

module.exports.subtract = function(a, b) {
        return a - b;
} 
```

We can also import the public functions into another Node.js script using `require()`, just as we do here:

```js
const {add, subtract} = require('./util')

console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5
```

If you are looking for a more in-depth [tutorial on CommonJS modules, check this out](https://blog.logrocket.com/es-modules-in-node-today/#commonjsmodulesystem).

On the other hand, library authors can also simply enable ES modules in a Node.js package by changing the file extensions from `.js` to `.mjs.`

For example, here's a simple ES module (with an `.mjs` extension) exporting two functions for public use:

```js
// util.mjs

export function add(a, b) {
        return a + b;
}

export function subtract(a, b) {
        return a - b;
}
```

We can then import both functions using the `import` statement:

```js
// app.mjs

import {add, subtract} from './util.js'

console.log(add(5, 5)) // 10
console.log(subtract(10, 5)) // 5
```

Another way to enable ES modules in your project can be done by adding a `"type: module"` field inside the nearest `package.json` file (the same folder as the package you’re making):

```js
{
  "name": "my-library",
  "version": "1.0.0",
  "type": "module",
  // ...
}
```

With that inclusion, Node.js treats all files inside that package as ES modules, and you won’t have to change the file to `.mjs` extension. You can [learn more about ES modules here](https://blog.logrocket.com/how-to-use-ecmascript-modules-with-node-js/).

Alternatively, you can install and set up a [transpiler like Babel to compile your ES module syntax down to CommonJS syntax](https://www.youtube.com/watch?v=7vGk8vFDGfA). Projects like React and Vue support ES modules because they use [Babel under the hood to compile the code](https://blog.logrocket.com/babel-vs-typescript/).

## Pros and cons of using ES modules and CommonJS modules in Node.js

### ES modules are the standard for JavaScript, while CommonJS is the default in Node.js

The ES module format was created to standardize the JavaScript module system. It has become the standard format for encapsulating JavaScript code for reuse.

The CommonJS module system, on the other hand, is built into Node.js. Prior to the introduction of the ES module in Node.js, CommonJS was the standard for Node.js modules. As a result, there are plenty of Node.js libraries and modules written with CommonJS.

For browser support, all major browsers support the ES module syntax and you can use `import`/`export` in frameworks like React and Vue.js. These frameworks uses a transpiler like Babel to compile the `import`/`export` syntax down to `require()`, which older Node.js versions natively support.

Apart from being the standard for JavaScript modules, the ES module syntax is also much more readable compared to `require()`. Web developers who primarily write JavaScript on the client will have no issues working with Node.js modules thanks to the identical syntax.

### Node.js support for ES modules

#### Older Node.js versions don’t support ES modules

While ES modules have become the standard module format in JavaScript, developers should consider that older versions of Node.js lack support (specifically Node.js v9 and under).

In other words, using ES modules render an application incompatible with earlier versions of Node.js that only support CommonJS modules (that is, the `require()` syntax).

But with the new conditional exports, we can build dual-mode libraries. These are libraries that are composed of the newer ES modules, but they are also backward-compatible with the CommonJS module format supported by older Node.js versions.

In other words, we can build a library that supports both `import` and `require()`, allowing us solve the issue of incompatibility.

Consider the following Node.js project:

```js
my-node-library
├── lib/
│   ├── browser-lib.js (iife format)
│   ├── module-a.js  (commonjs format)
│   ├── module-a.mjs  (es6 module format)
│   └── private/
│       ├── module-b.js
│       └── module-b.mjs
├── package.json
└── …
```

Inside `package.json`, we can use the `exports` field to export the public module (`module-a`) in two different module formats while resticting access to the private module (`module-b`):

```js
// package.json
{
  "name": "my-library",         
  "exports": {
    ".": {
        "browser": {
          "default": "./lib/browser-module.js"
        }
    },
    "module-a": {
        "import": "./lib/module-a.mjs" 
        "require": "./lib/module-a.js"
    }
  }
}
```

By providing the following information about our `my-library` package, we can now use it anywhere it is supported like so:

```js
// For CommonJS 
const moduleA = require('my-library/module-a')

// For ES6 Module
import moduleA from 'my-library/module-a'

// This will not work
const moduleA = require('my-library/lib/module-a')
import moduleA from 'my-awesome-lib/lib/public-module-a'
const moduleB = require('my-library/private/module-b')
import moduleB from 'my-library/private/module-b'
```

Because of the paths in `exports`, we can import (and `require()`) our public modules without specifying absolute paths.

By including paths for `.js` and `.mjs`, we can solve the issue of incompatibility; we can map package modules for different environments like the browser and Node.js while restricting access to private modules.

#### Newer Node.js versions fully support ES modules

In most lower Node.js versions, the ES module is marked as experimental. This means that the module lacks some features and is behind the `--experimental-modules` flag. Newer versions of Node.js do have stable support for ES modules.

However, it’s important to remember that for Node.js to treat a module as an ES module, one of the following must happen: either the module’s file extension must convert from `.js` (for CommonJS) to `.mjs` (for ES modules) or we must set a `{"type":` `"module"}` field in the nearest `package.json` file.

In this case, all code in that package will be treated as ES modules and the `import`/`export` statements should be used instead of `require()`.

### CommonJS offers flexibility with module imports

In an ES module, the import statement can only be called at the beginning of the file. Calling it anywhere else automatically shifts the expression to the file beginning or can even throw an error.

On the other hand, with `require()` as a function, gets parsed at runtime. As a result, `require()` can be called anywhere in the code. You can use it to load modules conditionally or dynamically from `if` statements, conditional loops, and functions.

For example, you can call `require()` inside a conditional statement like so:

```js
if(user.length > 0){
   const userDetails = require(‘./userDetails.js’);
  // Do something ..
}
```

Here, we load a module called `userDetails` only if a user is present.

### CommonJS loads modules synchronously, ES modules are asynchronous

One of the limitations of using `require()` is that it loads modules synchronously. This means that modules are loaded and processed one by one.

As you might have guessed, this can pose a few performance issues for large-scale applications that hundreds of modules. In such a case, `import` might outperform `require()` based on its asynchronous behavior.

However, the synchronous nature of `require()` might not be much of a problem for a small-scale application using a couple of modules.

## Conclusion: CommonJS or ES modules?

For developers who still use an older version of Node.js, using the new ES module would be impractical.

Because of the sketchy support, converting an existing project to the ES modules would render the application incompatible with earlier versions of Node.js that only support CommonJS modules (that is, the `require()` syntax).

Thus, migrating your project to use ES modules may not be particularly beneficial.

As a beginner, it can be beneficial and convenient to learn about ES modules given that they are becoming the standard format for defining modules in JavaScript for both client side (browser) and server side (Node.js).

For new Node.js projects, ES modules provide an alternative to CommonJS. The ES modules format does offer an easier route to writing isomorphic JavaScript, which can run in either the browser or on a server.

In all, EMCAScript modules are the future of JavaScript.

---

## 200’s only :heavy_check_mark: Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, [try LogRocket](https://logrocket.com/signup/). 

[![LogRocket Network Request Monitoring](https://blog.logrocket.com/wp-content/uploads/2019/12/network-request-filter-2-1.png)]
(https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. [Start monitoring for free](https://logrocket.com/signup/).