Comparing ECMAScript Modules and CommonJS

JavaScript

JavaScript started as a simple language used to make static websites more dynamic and interactive. However, projects written in JavaScript began getting increasingly complex a long time ago. Because of that, it quickly became apparent that we needed a way to break the code into smaller, manageable pieces. Throughout the years, people have had many different ideas on how to implement splitting JavaScript code into modules. In this article, we compare the most popular ones: ECMAScript Modules (ESM) and CommonJS.

CommonJS

The creator of Node.js knew that code needed to be organized into reusable modules. However, JavaScript did not have an official module system when Node.js was first presented in 2009. Because of that, Node.js introduced CommonJS modules.

Creating and exporting a module

In Node.js, every file is a separate module. To start working with CommonJS, let’s create a new file with a simple function.

sum.js

In every file, the variable represents the current module. We can use to make the function available for other modules to import and use.

Importing a module

To import a module, we need to use the function and provide the correct path.

index.js

CommonJS also supports the function, which allows us to import modules asynchronously.

index.js

ECMAScript Modules

The JavaScript language received a significant upgrade known as ES6 or ECMAScript 2015. Among other features, it included the official syntax for module management, ECMAScript Modules (ESM).

Creating and exporting a module

We have to use the  keyword to expose various values from a module.

sum.js

Using the above syntax, we can export as many values as we want. Besides that, modules can also contain a single default export.

subtract.js

Importing and exporting modules

To import ECMAScript modules, we have to use the keyword.

index.js

Please notice that we use a slightly different syntax to import a default export. What’s important is that the name we use with default imports does not have to match the default export.

This is one of the reasons some people prefer not to use the default exports.

ESM also supports the function, which allows us to import modules asynchronously.

index.js

Support for the ECMAScript Modules

While ECMAScript Modules were introduced around 2015, it took the community a while to catch up. However, now we can use ESM even natively with browsers.

If you want to know more about native JavaScript modules, check out Understanding native JavaScript modules

Bundlers such as Webpack started supporting ECMAScript Modules many years ago. They take our code that uses ESM across multiple JavaScript files and produce a single-file output. It can also split the bundle into more than one file to improve the performance.

Node.js started experimeting with support for ESM around version 8.5.0. To use it back then, we had to include the , though. In version 13.2.0, they removed the need to use the flag when using Node modules. However, using ESM in Node.js still resulted in a warning in the terminal saying that the feature is experimental. This warning does not appear since the release of 14.0.0 in April 2020. To use ESM with Node.js, we can add to the  file.

What’s interesting is that the implementation of ESM in Node.js might be slightly different if you’re used to working with Webpack, for example. We must provide the full path of the module we are importing, including the file extension.

ECMAScript Modules in TypeScript

TypeScript started supporting the ECMAScript Modules syntax back in TypeScript 1.5 in 2015. What’s crucial to understand is that it transpiles our code to use CommonJS under the hood by default.

index.ts

When we transpile the above file to JavaScript with the default configuration, we can see CommonJS.

index.js

TypeScript adds the flag to indicate that the file was compiled from ESM to CommonJS.

To use ESM with TypeScript and Node.js, we need to modify our file slightly.

tsconfig.json

Thanks to using , TypeScript will use ESM imports and exports instead of CommonJS. It’s crucial to remember that Node.js requires us to provide full paths to modules we import. When working with TypeScript that we want to run in a Node.js environment, we have to do that as well.

What might seem counterintuitive is that we have to provide the path that contains the extension even though we are writing TypeScript code.

index.ts

When we do all that, we can have a TypeScript application that runs in Node.js and uses ECMAScript Modules under the hood.

Managing dependencies

The issue of choosing between CommonJS and ECMAScript Modules gets more complicated when dealing with dependencies.

Projects that use ECMAScript Modules can use CommonJS modules using the syntax. However, projects that use CommonJS can’t import modules that use only ESM in any way other than through the asynchronous function. Because of that, many developers who create JavaScript libraries written with ECMAScript decide to ship both CommonJS and ESM code. This allows their libraries to be compatible with either module system.

However, not all developers want to deal with the hassle of publishing packages that work both with CommonJS and ESM. It is increasingly popular to ship only an ESM-compatible version of a library. Because of that, we need to be aware of how both CommonJS and ESM work and what their limitations are.

Summary

In this article, we’ve gone through how CommonJS and ECMAScript Modules work. We learned their syntax and how to use them with Node.js and TypeScript. Since not all developers want to go through the trouble of publishing packages that work both with CommonJS and ESM, it gets more and more important to understand the difference between those two module systems. Thanks to doing that, we are better equipped to choose the right module system for our project and adapt to any challenges related to it.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments