Slice of Dev Logo

CommonJS and ESM modules interoperability in NodeJS

Cover Image for CommonJS and ESM modules interoperability in NodeJS
Author's Profile Pic
Rajkumar Gaur

Introduction

NodeJS supports two types of module systems, CommonJS, and ESM (also known as ECMAScript modules or ES6 modules). You might have come across these terms around the internet and also due to errors being thrown at your face like the following.

Uncaught SyntaxError: Cannot use import statement outside a module

ReferenceError: require is not defined

Error [ERR_REQUIRE_ESM]: require() of ES Module not supported

NodeJS only supported CommonJS modules in the early days, then from around v8.x NodeJS started experimental support for ESM and things began to become a bit complex.

Given these two types of modules, interoperability came into question.

Let's look at how to resolve these kinds of errors and work out the kinks of using ESM and CommonJS together.

Importing CommonJS in ESM

ECMAScript modules are the official standard format to package JavaScript code for reuse.

You don't need to worry about which type of module you are importing when using ESM. Because ESM supports importing CommonJS modules using an import statement.

Let's create two files, one for CommonJS and another for ESM.

// cjs.cjs

const name = "Darth Vader";
const ability = () => {
  return "Can drive spaceship";
};

module.exports = { name, ability };

// esm.mjs

const name = "Messi";
const ability = () => {
  return "Can kick balls";
};

export default { name, ability };
export const extra = "He won the world cup";

Notice that we are using .cjs and .mjs extensions for the respective modules.

If the type property in package.json is omitted or set to commonjs we can use .js instead of .cjs and use .mjs for the files we want to behave as ESM.

If the type property in package.json is set to module we can use .js instead of .mjs and use .cjs for the files we want to behave as CommonJS.

// esm-consumer.mjs

// importing esm in esm
import messi from "./esm.mjs";

console.log(messi.name, messi.ability());

// importing cjs in esm
import darthVader from "./cjs.cjs";

console.log(darthVader.name, darthVader.ability());

As you can see in the example above, we can import the CommonJS modules using the import statement just like if we had imported an ESM module.

We can also destructure the imported object from CommonJS just like we destructure an import from ESM.

// importing cjs in esm
import { name, ability } from "./cjs.cjs";

Importing ESM in CommonJS

CommonJS doesn't support importing ESM modules with require statements. This is because ES modules have asynchronous execution.

Instead, we can use dynamic import() statements to import ESM in CommonJS. Also, note that the import() statement is not only limited to being used in CommonJS modules, it can be used in ESM modules too.

This import() call is not the same as the import statement in ESM modules, only the spelling is identical.

import() returns a promise and the default and named exports can be accessed by destructuring the resolved value of the promise.

// importing cjs in cjs
const darthVader = require("./cjs.cjs");

console.log(darthVader.name, darthVader.ability());

// importing esm in cjs
import("./esm.mjs").then(({ default: messi, extra }) => {
  console.log(messi.name, messi.ability(), extra);
});

You might get errors while importing libraries like lodash-es in your CommonJS files because those libraries export an ESM module. Use dynamic import() calls for these libraries instead of require() to fix those errors.

Conclusion

I hope this article helped you in making sense of the two module systems in NodeJS and their interoperability. Thanks for reading!

Useful Links


Cover Image for React Interview Experience

React Interview Experience

This blog is about my recent React interview experiences and some interesting questions that were asked. These questions might help you prepare for your next interview. Guess The Output | useState vs useReducer | useCallback and useMemo | Redux Vs Context API | Manage The Focus Of Elements Using React | Why is useRef used | Coding Problem.

Author's Profile Pic
Rajkumar Gaur
Cover Image for Streams in NodeJS

Streams in NodeJS

Node.js Streams are an essential feature of the platform that provide an efficient way to handle data flows. They allow for processing of large volumes of data in a memory-efficient and scalable way, and can be used for a variety of purposes such as reading from and writing to files, transforming data, and combining multiple streams into a single pipeline

Author's Profile Pic
Rajkumar Gaur