CommonJS vs ES Modules in Node.js - A Detailed Comparison

Read it in 13 Mins

Published
02nd Feb, 2023
Views
1,732
CommonJS vs ES Modules in Node.js - A Detailed Comparison

In most programming paradigms, we can find a module system that allows us to organize our code in different self-organized parts or include code from other libraries. Combining all these results in a larger, more complex application. Know further about CommonJS vs ES modules in this article. Since its launch, the CommonJS module system has been the default module system within Node.js ecosystem. However, a new module system was added post version v8.5.0, which is the ES module. Earlier it was experimental, but from v13.2.0, it has been stabilized and incorporated as standard. 

In a browser, the execution of JavaScript modules depends upon import and export statements. These statements load and export ECMAScript modules which are ES modules, respectively. The ES module usage format is the official standard to write JavaScript for maximum reusability and is what most web browsers natively support. 

However, NodeJS supports the CommonJS module format by default, which is loaded using require() function, and the variables and functions are exported with the help of module. exports. Further, explore Node.js best course to increase your understanding.

CommonJS vs ES Modules in Node.js 

Let us have a look at CommonJS vs ES modules in depth:

Functionality 

CommonJS 

ES Module 

Functionality 

Works with the Node.js platform 

Works with the web browser environment 

Compilation 

Compiled into AMD modules 

Does not require a module loader like AMD

Dependencies 

All dependencies are listed in the same file 

Reference any other module in the same package available on the global namespace 

Type-checking 

No type-checking capabilities 

Robust typing support via imports

Dependency Packaging 

Packaging up functionality into small pieces 

Declare dependencies between modules 

File Structure 

Flat files 

References to other modules 

Export 

Exports in the same file 

Exports scattered through codebase 

Import 

No import functionality 

Must use a require statement to access exported functions and properties

Comparison Between CommonJS vs ES Modules 

Check further differences between CommonJS and ES Modules along with the parameters:

CommonJS vs ES Modules: Functionality 

CommonJS modules are similar to ES modules, but there are a few key differences. First, CommonJS modules must be loaded from a module repository, such as npm.

Second, CommonJS modules can only be accessed from within the context of a Node.js application. CommonJS modules do not have exports or prototypes like ES modules do. Finally, the require() function in CommonJS is not equivalent to the import() function in ES Modules.

CommonJS vs ES Modules: Compilation 

CommonJS is a module system inspired by the AMD API from Microsoft. It allows modules to be written in a similar way to AMD modules, with the main difference being that CommonJS modules can be loaded into any node.js environment.

ES Modules are a new module system developed by Google which allows for greater modularity and reuse across different programming languages. The advantage of using ES Modules is that they can be compiled into native code which makes them faster and more efficient than CommonJS modules.

CommonJS vs ES Modules: Dependencies 

CommonJS modules are AMD-compliant and can be used in both Node.js and browsers. ES modules are not AMD-compliant but are supported in browsers through the webpack module loader.

CommonJS modules have a few dependencies that must be installed before they can be used: nodejs, npm, and yeoman. These dependencies can be installed using the following commands:

npm install -g nodejs npm install -g npm yeoman 

ES modules do not have any dependencies other than the module loader itself. However, some development tools may require additional libraries, such as Babel. To install these tools, you can use the following command: 

yarn add babel babel-cli babel-preset-env

CommonJS vs ES Modules: Typescript 

CommonJS modules are a popular way to modularize JavaScript code. They allow you to export and import modules using the export and import keywords, respectively.

ES Modules are a newer way to modularize JavaScript code. Unlike CommonJS modules, which rely on exports and imports, ES Modules use the module keyword. This allows you to define modules in a more declarative way without having to use the export and import keywords. Enroll in Full Stack online course to learn the essential modules in node js.

CommonJS vs ES Modules: File Structure 

The main difference between CommonJS and ES modules is the file structure. With CommonJS, all of the dependencies for a project are stored in one file called node_modules/. With ES modules, each dependency is stored in its files. This allows developers to better control how their dependencies are used and makes it easier to version software.

CommonJS vs ES Modules: Export 

CommonJS modules are a popular and efficient way to modularize code. They use the export keyword to make functions and variables accessible from other modules.

ES modules are a newer version of the CommonJS module format. They use the export keyword but also include a require() function that automates the process of including other ES modules in your project.

CommonJS vs ES Modules: Import 

ES modules provide a way to group related code together for reuse. CommonJS, on the other hand, lets you use any module from the global namespace.

To answer your question about CommonJS or ES modules which is better, CommonJS modules are typically used in Node.js applications, as they allow for a more centralized approach to code management and require no additional configuration on the part of the developer. 

ES modules are popular in browser-based applications because they can be loaded asynchronously and don't require an underlying runtime environment like Node.js.

What is CommonJS in Node.js? 

In Node.js each of the JavaScript files is handled as an individual separate CommonJS module. This infers that the variables, functions, classes, etc., which are present inside of it, are not, by default, directly accessible to other files. We need to explicitly describe the module system as to which parts of the code should be exported and which not. CommonJS modules are the original way to package JavaScript code for Node.js. 

This loading paradigm was designed with server-side JavaScript in mind and is not suitable for client-side applications. Modules are loaded synchronously and are accessed in the same order that JavaScript locates them. 

Symbols that are defined inside a JavaScript file are considered modules when they are exported. It could be variables, functions, objects, classes, etc. Let us take an example of string manipulation JavaScript file stringOps.js, which has several functions, like one to remove all spaces from the string. 

exports.removeSpace = (str) => str.removeSpace() 

Any other JavaScript file can then import and use this module, as follows: 

const stringOpsModule= require('stringOps.js') 
stringOpsModule.removeSpace('This is a test for whitespaces.') 

This way of importing can be extended to import multiple values as well. Let's take an example of some other functions as well 

exports.nextLine = ‘\n’ 
exports.singleWildCard = ‘?’ 
exports.multiWildCard = ‘*’ 

and then import them individually in other files as required using destructing assignment. Following is an example :

const { nextLine, singleWildCard, multiWildCard } = require('./stringOps.js') 

There is also a way to just export one specific value, as we can see in the example: 

//example.js 
module.exports = value 
and import as follows using :
const value = require('./example.js') 

The same way, we can import and use modules from NPM packages (i.e. from the node_modules folder). Let’s take the example of chalk library 

const chalk = require("chalk") // needs to be called after npm install 
console.log(chalk.blue("Hello world printed in blue")); 

What is ES in Node.js? 

The ECMAScript modules (in short ES modules) is a JavaScript modules format which is the official standard format to package JavaScript code for reuse. The ES modules format generally offers an easier route to writing isomorphic JavaScript, which can run in the browser or on a server.

When Node.js came into existence, the ES modules support didn't exist, and so Node.js decided to use CommonJS Modules. While CommonJS as an organization is no longer an active concern, Node.js and npm have evolved the specification to create a very large JavaScript ecosystem. Due to this the ES module got its first support starting from version v8.5.0. At this time we needed to use it with an experimental flag ‘--experimental-modules’. 

But starting from version v13.2.0, the support has been stabilized, so this tag is not required anymore. As a result, the Node/npm ecosystem is extended to both server and client, and is growing rapidly. ES Modules are defined using a variety of import and export statements, but Node.js uses ES modules format if: 

  • The file extension for the module is ‘.mjs’ or the module's nearest parent folder has { "type": "module" } in its package.json :

An easy way to tell Node.js to treat the modules in ECMAScript format is to use the .mjs file extension. 

Let us take the following ES module example weekday-from-date.mjs exports a function weekdayFromDate(), which is used to determine the day of the week of an arbitrary date: 

// weekday-from-date.mjs (ES Module) 
const WEEKDAY = ['Monday', 'Tuesday', ‘Wednesday', 'Thursday', 
'Friday', 'Saturday', 'Sunday']; 
export function weekDayFromDate(date) { 
if (!(date instanceof Date)) { 
date = new Date(date); 
} 
return WEEKDAY[date.getWeekday()]; 
} 
// weekday.mjs (ES Module) 
import { weekDayFromDate } from './weekday-from-date.mjs'; 
const dateString = process.argv[2] ?? null; 
console.log(weekDayFromDate(dateString)); 

Now if we run weekday.mjs module in command line: 

node ./weekday.mjs "2022-06-08" 

Wednesday is printed in the terminal. 

  • The argument --input-type=module is present in the config, and the module's code is passed as a string using --eval="<module-code>" argument or from STDIN. By default .js files in Node.js are considered CommonJS modules. To make .js files as ES modules, we need to set the "type" field as "module" in the package.json. Following presents an example for the same :
{ 
"name": "my-app", 
"version": "1.0.0", 
"type": "module", 
// ... 
} 

Now all ‘.js’ files inside the folder containing such package.json execute as ECMAScript modules, and we don’t need to alter the filenames. Let’s take the example of the weekday-from-date module’s example here as well :

Let's rename weekday-from-date.mjs to weekday-from-date.js and weekday.mjs to weekday.js and keep the import and export syntax the same. Then we set "type" field as "module" in the package.json, and we can see Node.js executes these modules as ECMAScript ones. 

node ./weekday.js "2022-06-07" 

Tuesday is printed in the terminal. 

Above was an example for local imports, but we can use the same to import other ES modules. The specifier is the string literal representing the path from where to import the module. Like in the example below, 'path' is a specifier: 

// 'path' is the specifier 
import module from 'path' 

There are 3 kinds of specifiers in Node.js :

1. Relative: Importing a module using a relative specifier would resolve the path of the imported module relative to the current (importing) module location. Relative specifiers usually start with '.', '..', or './':

// Relative specifiers: 
import module1 from './module1.js'; 
import module2 from '../folder/module2.mjs'; 

When using relative specifiers, indicating the file extension (.js, '.mjs', etc.) is obligatory. 

2. Bare: A bare specifier starts with a module name and imports modules from node_modules or the built-in Node.js modules. For instance, if we have installed the lodash-es package in node_modules, then we can access that module using a bare specifier:

// Bare specifiers: 
import lodash from 'lodash-es'; 
import intersection from 'lodash-es/intersection'; 

Using bare specifiers we can also import the Node.js built-in modules: 

import fs from 'fs' 

3. Absolute: An absolute specifier imports modules using an absolute path:

// Absolute specifier: 
import module from 'file:///usr/opt/module.js'; 

Also you can check out the full stack online course to work and experience everything in action. 

What's the Difference Between CommonJS and ES?

Under the default scenario Node.js treats all 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. 

Since by default, all the code is considered CommonJS, we need to explicitly mention the type specifying it by either the file extension(.mjs) in which case it then overrides the default standard of CommonJS and makes ES as default, or by using type:”module” in the package.json, which results in the same behavior.

Since both types are different, it is only feasible to use one at a time. In contrast, conditional exports allow us to build libraries that are both backward-compatible with CommonJS modules and newer ES modules. Consider the following example 

example-node-library 

├── lib/ 
│ ├── module-exampleA.js (commonjs format) 
│ ├── module-exampleA.mjs (es6 module format) 
│ └── private/ 
│ ├── module-exampleB.js (commonjs format) 
│ └── module-exampleB.mjs (es6 module format) 
├── package.json 
└── … 

Inside package.json, we can use the exports field to export the public module (module-exampleA) in two different module formats while restricting access to the private module (module-exampleB): 

// package.json 
{ 
"name": "example-node-library",
"exports": { 
".": { 
"... 
}, 
"module-exampleA": { 
"import": "./lib/module-exampleA.mjs" 
"require": "./lib/module-exampleA.js" 
} 
} 
} 

Now that we have provided the following information about our example-node-library package, we can use it anywhere it is supported. 

// For CommonJS 
const module-exampleA = require('example-node-library/module-exampleA') 
// For ES6 Module 
import module-exampleA from 'example-node-library/module-exampleA' 

Our public modules can be imported and required without specifying absolute paths because of the paths in different exports. Thus, by including '.js' and '.mjs', we are able to resolve the compatibility issue. Package modules can be mapped to different environments, such as a browser and Node.js, while private modules can be restricted. 

We will be looking at the pros and cons of each in the next section. ES modules are the current standard for JavaScript, while CommonJS is the default standard in Node.js 

Merits and Demerits of CommonJS and ES 

CommonJS

1. Support for Older Versions 

In case of legacy code or older version of node, CommonJS has full and stable support, since it is the default standard. Versions preceding v8.5.0 have to be using this only. Also, upto very recently until v13.2.0, ES was marked experimental, there also CommonJS has a great support. 

2. Flexibility with Module Imports 

Import statements are allowed only at the beginning of the file if we call them elsewhere. The control moves the expression to the beginning of the file or throws an error. As a result, require() can be called anywhere in the code since it is parsed at runtime. As well as loading modules conditionally or dynamically, it can also load modules from if statements, conditional loops, or functions, e.g :

if(str.length > 0) { 
const StringMeta = require(‘./stringMeta.js’); 
… 
} 

Here, we load the module stringMeta only if the string is non-empty. 

3. Module Load is Synchronous

One of the drawbacks of using require() is that modules are loaded and processed sequentially one by one since it does the loading synchronously. This can pose rigorous performance issues for large-scale applications that can load hundreds of modules. It might not be a problem for a small-scale application using a limited number of modules. 

ES Module 

1. Allows Import of CommonJS from ES

NodeJS allows us to import CommonJS modules from ES Modules, since in this case, module. exports simply become the default export which we might import as such. 

2. Can be Executed at Both Parse Time and Runtime 

It is for this reason that imports are "hoisted", as they are implicitly moved to the top. The import syntax cannot be used anywhere in the code, therefore. The benefits of this are that errors can be caught upfront and we get better support from developer tools for writing valid code. However, using the import() function can give us the same benefit of dynamic loading. 

3. Load from URL Support 

ECMAScript 6 also provides support that modules can be loaded from a URL. This new improvement not only makes loading more complicated but also slow.

4. Requires File Extension 

A file extension must be provided when using the import keyword. Directory indexes (e.g., './directory/index.js') must be fully specified. 

5. Is Asynchronous 

Since the load is asynchronous, it makes more sense to use it to load many modules. 

Which One is Better, CommonJS or ES? 

Both CommonJS and ES are valid options; as we have just seen, it has evolved a lot in the past decade, and we have options for a good amount of versatility. But mostly, we can classify the decision-making into broadly two clauses :

  • Since ES Modules have been standardized for many years, it is often better to use them when starting a new project. Since the release of version 14, released in April 2020, NodeJS has had stable support for this. There is a lot of documentation and examples, and it is also interoperable with CommonJs. ES Modules support has already been added to many libraries by new package maintainers. 
  • If we are maintaining an existing NodeJS project using CommonJS, or if we are using an older version of Node.js, things may be different. It is good news that no existing code needs to be migrated at the moment. NodeJS still uses CommonJS as its default module system and it is unlikely to change any time soon. A conversion to the ES modules would also make the application incompatible with earlier versions of Node.js because of the sketchy support. 

Conclusion

It is better to migrate to ES Modules while still using CommonJS. This can be accomplished by tools like Babel or Typescript, allowing us to decide more easily to switch to ES Modules later. Remember, for further insight into the action, opt for KnowledgeHut Node.js best course to polish your skills. To know how to install and manage Node.js check this- Installing and Managing Node.js Using NVM

Profile

Gaurav Roy

Author

I am an avid coder, software developer by profession and computer science post graduate from IIT(ISM) Dhanbad. I have 6.5+ yrs of development experience, working with cross platform mobile development in both iOS, Android and Web. I love to code and design robust systems, exploring and exploiting various cutting edge tech stacks available including Artificial Intelligence/machine Learning and evolutionary computing technologies, my post graduate thesis being based on the field. Apart from academics, I am a guitar player and singer.

Share This Article
Want to become a sought-after web developer?

Avail your free 1:1 mentorship session.

Select
Your Message (Optional)

Frequently Asked Questions (FAQs)

1What is CommonJS module in Node.js?

CommonJS module system is the default module system within the NodeJS ecosystem. CommonJS modules are the original way to package JavaScript code for Node.js. In terms of the loading paradigm for CommonJS, modules are loaded synchronously and are processed in the same order that the JavaScript runtime locates them. 

2What is ES module in Node.js?

In a browser the execution of JavaScript modules depends upon import and export statements. These statements load and export ECMAScript modules, which are the ES modules. The ES module usage format is the official standard to write JavaScript for maximum reusability and is what most web browsers natively support.

3What is the major difference between CommonJS and ES module?
  • CommonJS is synchronous whereas ES is asynchronous
  • CommonJS supports only runtime resolution whereas ES supports both parse time and runtime
  • ES supports interoperability whereas CommonJS doesn't 
  • CommonJS is the default standard whereas ES recently achieved stable support
  • Common JS uses require() on the other hand ES uses import export
4What are the benefits of CommonJS?

CommonJS is the default standard and is supported in all Node.js versions. It is resolved at Runtime and has a wide amount of Dev support since it has been there since the beginning of Node.JS

5Does Node.js use CommonJS?

Yes, CommonJS is the default Standard for module inclusion. 

Upcoming Web Development Batches & Dates

NameDateFeeKnow more