A more detailed article on ESM and CommonJS modules in Node.js

A more detailed article on ESM and CommonJS modules in Node.js

Daily short news for you
  • For over a week now, I haven't posted anything, not because I have nothing to write about, but because I'm looking for ways to distribute more valuable content in this rapidly exploding AI era.

    As I shared earlier this year, the number of visitors to my blog is gradually declining. When I looked at the statistics, the number of users in the first six months of 2025 has dropped by 30% compared to the same period last year, and by 15% compared to the last six months of 2024. This indicates a reality that users are gradually leaving. What is the reason for this?

    I think the biggest reason is that user habits have changed. They primarily discover the blog through search engines, with Google being the largest. Almost half of the users return to the blog without going through the search step. This is a positive signal, but it's still not enough to increase the number of new users. Not to mention that now, Google has launched the AI Search Labs feature, which means AI displays summarized content when users search, further reducing the likelihood of users accessing the website. Interestingly, when Search Labs was introduced, English articles have taken over the rankings for the most accessed content.

    My articles are usually very long, sometimes reaching up to 2000 words. Writing such an article takes a lot of time. It's normal for many articles to go unread. I know and accept this because not everyone encounters the issues being discussed. For me, writing is a way to cultivate patience and thoughtfulness. Being able to help someone through my writing is a wonderful thing.

    Therefore, I am thinking of focusing on shorter and medium-length content to be able to write more. Long content will only be used when I want to write in detail or delve deeply into a particular topic. So, I am looking for ways to redesign the blog. Everyone, please stay tuned! 😄

    » Read more
  • CloudFlare has introduced the pay per crawl feature to charge for each time AI "crawls" data from your website. What does that mean 🤔?

    The purpose of SEO is to help search engines see the website. When users search for relevant content, your website appears in the search results. This is almost a win-win situation where Google helps more people discover your site, and in return, Google gets more users.

    Now, the game with AI Agents is different. AI Agents have to actively seek out information sources and conveniently "crawl" your data, then mix it up or do something with it that we can't even know. So this is almost a game that benefits only one side 🤔!?

    CloudFlare's move is to make AI Agents pay for each time they retrieve data from your website. If they don’t pay, then I won’t let them read my data. Something like that. Let’s wait a bit longer and see 🤓.

    » Read more
  • Continuing to update on the lawsuit between the Deno group and Oracle over the name JavaScript: It seems that Deno is at a disadvantage as the court has dismissed the Deno group's complaint. However, in August, they (Oracle) must be held accountable for each reason, acknowledging or denying the allegations presented by the Deno group in the lawsuit.

    JavaScript™ Trademark Update

    » Read more

The Issue

Previously, I wrote about various types of modules in Node.js and JavaScript. I briefly mentioned CommonJS, AMD, and ESM modules, which you can review in the articles "Understanding require in Node.js" and "Understanding modules in Node.js. Why are there so many module types?". However, I didn't go into depth about them.

Many people are unsure when to use require and when to use import. Can you use both in the same project? In today's article, let's explore how these two types of modules work in Node.js to answer these questions.

How ESM and CommonJS work?

It can be said that the lack of a clear module system in JavaScript from the beginning has led to the complexity we face today. Early JavaScript communities had to create various module systems for it, including AMD, UMD, and more. When Node.js emerged, it chose CommonJS as its default module system.

Realizing its shortcomings, ECMAScript eventually introduced the official module system for JavaScript, which is ESM. JavaScript was immediately updated with this module type, but perhaps it was a bit late because there were already many packages created using unofficial module systems. Nevertheless, whether sooner or later, it's only a matter of time until ESM becomes widespread.

In summary, CommonJS was born for use in Node.js, while ESM is the official module system of JavaScript. However, ESM is only supported from Node.js 12+ onwards, while in modern browsers, ESM can generally run. Can browsers run CommonJS? The answer is no. But we can still create packages that can run in both environments.

So can the browser run all ESM modules? The answer is still no. Simply put, if a module contains Node.js-specific functions that the browser lacks, it won't run. Conversely, ESM may not necessarily run in Node.js because it may contain functions not supported by Node.js. In the end, whether it runs or not depends on the developer's support.

For example, creating a module with CommonJS:

// add.js file
// or add.cjs file

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

module.exports = add;

The .cjs extension is entirely valid and is used to indicate to Node.js that this is a module using CommonJS.

Then we can use the module using require syntax:

const add = require('./add.js')

add(1, 2);

Similarly, an ESM module will look like this:

// add.js file
// or add.mjs file

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

export default add;

Similarly, .mjs is also a valid extension and indicates that this is an ESM module.

Use ESM module using import syntax:

import add from './add.js';

add(1, 2);

So, the most significant difference between CommonJS and ESM lies in the module import/export syntax and the use of import and require to use them.

Can CommonJS and ESM be Used Together?

The answer is yes. You can follow the instructions below.

Importing CommonJS Modules into an ESM Project

It's straightforward, use the import syntax as usual:

// index.js

import add from './add.cjs';

add(1, 2);

Importing ESM Modules into CommonJS

Because require is a synchronous function, it cannot be used to import ESM modules. Instead, to import ESM into CommonJS, we will use import. import returns a Promise, and the result contains a default field pointing to the default export of the ESM module.

// index.js

(async function () {
  const add = (await import('./index.mjs')).default;

  add(1, 2);
})();

Since await must be called within an async function, this writing style is necessary. Alternatively, you can use a newer version of Node.js that supports Top-level await.

Sometimes you might find projects using both require and import syntax side by side. This is likely due to the use of some transpilation tools like TypeScript, Webpack, Rollup, etc. In essence, we can freely write require or import syntax, but after building, the tool will convert the code to a consistent CommonJS or ESM syntax according to preconfigured settings.

Universal Modules

Universal modules refer to modules that can work in both Node.js and browsers. This means depending on the usage context, they will automatically use either CommonJS or ESM syntax.

To achieve this, it's simple. Declare the file paths used in each environment in the package.json file.

{
  ...
  "main": "cjs/index.js",
  "module": "es6/index.js",
  ...
}

With main pointing to index.js using CommonJS syntax and module pointing to index.js using ESM syntax. When you install and use that package in either Node.js or a browser environment, it will automatically understand and choose the compatible module type. This way, you can write code compatible with both browsers and Node.js in the same package.

"But wait, do I have to write code twice in two places?" You can, but hardly anyone does that because build tools help us create Universal modules. You write the code once, and through a build step, it generates code for both environments.

There are many build tools you can find, such as Webpack, Rollup, and a relatively new and fast one called esbuild.

Readers can refer to the documentation of the tool they want to use or search for examples and frameworks to get started quickly on GitHub.

Conclusion

CommonJS works in Node.js but not in browsers. ESM is supported by all modern browsers and the latest versions of Node.js 12+.

Many tools in the JavaScript ecosystem were developed in Node.js, and Node.js has only recently begun to support ESM. Therefore, most projects still use CommonJS.

If you're starting a new Node.js project and are unsure whether to fully support ESM or not, consider the fact that many common npm packages still use CommonJS. Although ESM can potentially support importing modules using CommonJS, it's still worth considering potential issues and troubleshooting them later.

In the end, Universal is a concept that refers to packages that can work in various environments such as Node and browsers. By declaring file paths to import files in package.json, it instructs the environment on how to import compatible module types.

References:

Premium
Hello

The secret stack of Blog

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

As a developer, are you curious about the technology secrets or the technical debts of this blog? All secrets will be revealed in the article below. What are you waiting for, click now!

View all

Subscribe to receive new article notifications

or
* The summary newsletter is sent every 1-2 weeks, cancel anytime.

Comments (3)

Leave a comment...
Avatar
Phạm Tiến Đạt1 year ago
  • Em cám ơn anh. Bài viết của anh rất hay
Reply
Avatar
Xuân Hoài Tống1 year ago

Cảm ơn e nhé, ghé blog a thường xuyên nha vì a viết hàng tuần á.

Avatar
Phạm Tiến Đạt1 year ago
  • Hiện tại em viết một NPM package và dùng Typescript để compie ra hai file một là cjs và esm, sau đó em dùng cjs trong main và esm trong module ở package.json như vậy thì nếu em install package này dùng trong Node Server thì mặc định sẽ dùng ở cjs phải không anh và ngươc lại ở các dự án Reacts thì sẽ dùng ở ESM ạ. Mong anh trả lời ạ, em cám ơn anh.
Reply
Avatar
Xuân Hoài Tống1 year ago

Ồ nếu thế thì e có thể tham khảo phần Universal modules trong bài viết kia, nó dùng để cấu hình cho Node hoặc Trình duyệt biết nên import tệp nào. Hoặc tham khảo 2 thuộc tính main và browser tại https://docs.npmjs.com/cli/v10/configuring-npm/package-json#main

Điều quan trọng là phải có cấu hình 2 thuộc tính này trong package.json thì Node/Trình duyệt mới hiểu được em đang muốn dùng tệp nào ở môi trường nào.

Avatar
Phạm Tiến Đạt1 year ago

Dạ em chào anh, em có một câu hỏi ạTheo như anh đề cập thì Commonjs là module trong Nodejs, và Esm là module được sử dụng trong trình duyệt vậy khi import Commonjs vào Esm thì có cần phải qua build tool, webpackage, babel để convert không anh.

Reply
Avatar
Xuân Hoài Tống1 year ago

Chào em, một câu hỏi thú vị.Khi em muốn import commonjs vào esm thì anh hiểu em đang làm việc với dự án Node.js, Node ở các phiên bản hiện tại hỗ trợ cả commonjs và esm nên em có thể làm như thế mà không cần qua build tools nào cả, Node tự làm. Nhưng như thế nhiều vấn đề có thể xuất hiện, ví dụ như em chạy các phiên bản Node thấp hơn hay các phiên bản Node cao hơn có thể ngừng hỗ trợ commonjs cho nên tốt nhất là vẫn nên dùng thêm các build tools để được hỗ trợ lâu dài và đa phiên bản.Còn trong chiều ngược lại, em đang làm dự án của trình duyệt thì muốn import commonjs vào esm thì phải qua build tools rồi em nhé!