CommonJS and EcmaScript modules in NodeJS

Rajkumar Gaur
SliceOfDev

--

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.

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 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

Find more content like this at SliceOfDev.com

--

--