DEV Community

Alessio Michelini
Alessio Michelini

Posted on • Edited on

Why you shouldn't mix ES Modules and CommonJS

When working with JavaScript, it's important to follow best practices to ensure smooth and efficient code. One such best practice is to avoid mixing ES modules syntax and CommonJS syntax in your codebase. In this article, we’ll examine why you should avoid this practice, and how to transition your code to use one syntax consistently.

What's the difference?

Firstly, let's look at what exactly is ES modules syntax and CommonJS syntax. ES modules syntax is a standardized way of organizing code in small, reusable units that can be imported or exported in other modules.

Here's an example of how an ES Module might be structured:

// greeting.js
export const greeting = 'Hello, World!';

// main.js
import { greeting } from './greeting.js';

console.log(greeting); // Output: 'Hello, World!'
Enter fullscreen mode Exit fullscreen mode

In the example above, we define a module named "greeting.js" that exports a constant named "greeting". Then, in a file named "main.js", we import the "greeting" constant from the "greeting.js" module and log it to the console.

ES Modules have several advantages over other methods of organizing code in JavaScript. They provide better encapsulation, making it easier to manage dependencies and avoid naming conflicts. They also enable better browser caching and optimization, as the browser can load individual modules on demand rather than loading a large script file all at once.

What about CommonJS?

On the other hand, CommonJS syntax is the traditional way of organizing code in Node.js, allowing you to define modules and their exports.

CommonJS modules have a simple structure that consists of an object, which defines properties that can be exported and imported in other modules. Here's an example of how a CommonJS module might be structured:

// greeting.js
module.exports = 'Hello, World!';

// main.js
const greeting = require('./greeting.js');

console.log(greeting); // Output: 'Hello, World!'
Enter fullscreen mode Exit fullscreen mode

In the example above, we define a module named "greeting.js" that exports a string value. Then, in a file named "main.js", we require and import the "greeting" module and log its value to the console.

Why can't I use them at the same time?

So why should you not mix these two types of syntax? Here are a few reasons:

  1. Syntax conflicts: ES modules syntax and CommonJS syntax use different syntax conventions. The use of different conventions in the same module can create conflicts, leading to syntax and runtime errors.

  2. Compatibility issues: Not all JavaScript environments support ES modules syntax. Attempting to use them in a CommonJS environment, or vice versa, can lead to compatibility issues and errors. For example, if you use ES modules syntax in a Node.js environment, you may encounter unexpected errors when consuming modules from NPM.

  3. Maintenance concerns: Mixing syntax can make it difficult to maintain or update your code, as different syntax conventions may require different tools, which can lead to added complexity and confusion.

  4. Consistency: You should always stick with one strategy and not mix two of more at the same time, this can create confusion on other developers that read your code.

To avoid these issues, it's best to choose one syntax and stick with it consistently throughout your codebase. If your codebase uses CommonJS syntax, avoid introducing ES modules syntax. If you're working with ES modules syntax, avoid mixing in CommonJS modules, if possible.

However, there may be situations where you need to use code from modules that are written in the other syntax. In such cases, it's best to use a transpiler like Babel or a bundler like Webpack to ensure compatibility. These tools can convert one syntax to the other, allowing you to use the modules you need without introducing conflicts.

Conclusion

In conclusion, avoiding the mixing of ES modules syntax and CommonJS syntax is a good practice for any JavaScript project. By sticking to one syntax convention and using appropriate tools, you can ensure that your codebase is organized, compatible, and easy to maintain.

Disclaimer

Parts of this article was created with the help of AI.

Top comments (11)

Collapse
 
noherczeg profile image
Norbert Csaba Herczeg

It has never been a question about "would you like to, or not", rather a "do I have a choice or not".

Collapse
 
tracker1 profile image
Michael J. Ryan • Edited

In other words, don't use npm. Or only use the parts of npm that fit cleanly into what you have. Or just don't bother to update or change a code base.

As opposed to you know, clearly documenting the edges and making them easier to use.

The fact that Node specifically chose to make thisb harder is what lead to your points. This was done in purpose rather than following the conventions that Babel has in place well before Node added esm support.

Yes, it's closer to the browser convention. But was a half measure from the start and a purposely painful one.

Collapse
 
t_bogard profile image
Erick Rodriguez

You are forgetting scenarios where you need both of them. For example: Angular lazy loading strategy requires you to use require on the endpoint you want to load a component in demand.

It is not that you must not use both of them, but the scenario where you need them

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

Very good points.

We've ended up with a back end framework where the core stuff is CommonJS but modules that are built into it are ES modules. We ended up supporting both because our modules tend to be colocated with the UI that consumes them (which is all ES modules) and IDE support for refactoring is better for ES modules. At some point we may refactor the whole of the framework too, but that is going to have to sit as tech debt for now as it's rarely touched.

Collapse
 
kerrickchan profile image
Kerrick Chan

I think it's simple. just go to TypeScript other than prototyping or simple scripts.

Collapse
 
noherczeg profile image
Norbert Csaba Herczeg

TS has nothing to do with bundle output formats. You can bundle TS code also in CJS.

Collapse
 
kerrickchan profile image
Kerrick Chan

No, already support for a while. You may bundler other than CJS. it's popular and old. Also, ESM accept CJS but cannot vice versa
typescriptlang.org/docs/handbook/e...

Thread Thread
 
noherczeg profile image
Norbert Csaba Herczeg

What I said is, mixing TS into the topic is pointless because it can produce any output format.

Thread Thread
 
franzzemen profile image
Franz Zemen

BTW, CJS can load ESM with dynamic import. To get TS types use the import type syntax.

And..the article above makes claims without proof. Know your language, i.e understand the why of things and intermixing CJS and ESM, if needed, becomes straightforward.

Collapse
 
hassansuhaib profile image
Hassan Suhaib

I was working with NestJS on a project and there was library that I desperately needed but it only supported CommonJS so my code was crashing. Took me quite a while to fix that.

Collapse
 
malgorzataxkowal profile image
malgorzataxkowal

I face a similar problem. I'm using esm modules but for some reason lighthouse library (only parts- startFlow function) can not be imported cause of incorrect type [stackoverflow.com/questions/777223...]