The Ongoing War Between CJS & ESM: A Tale of Two Module Systems
Tags: javascript, modules, cjs, esm
Published: 2024-07-11 22:27:03

(This was originally an in-person talk I gave, so the following graphics are from said presentation slides, the bulk of which are attributed to [SlidesGO](https://slidesgo.com/); check them out for awesome & free slides! Just be sure to credit them during your presentation <3)

---

## _**A**_ battle has raged on for _millennia_, claiming numerous casualties in its wake, where no single individual, corporation nor entity was safe from the wreckage that followed in its trail; hidden deep within the binary bunkers, there festered something so awful, so _terrible_ that mankind is still reeling from it today. 
---
##Of course, I'm speaking of the dreaded 20-year war that is commonly known as...

---

## COMMON JAVASCRIPT (CJS)
## VS 
## ECMA SCRIPT MODULES (ESM)

---

So with **_that_**, my friends, let's dive into the ongoing war between the two different kinds of module implementations that exist within our favorite language, known as JavaScript; CJS & ESM.

---

![What even is CJS and ESM?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iwrz4i43a6mj7vdyes3r.JPG)

Yeah, what _**is**_ ESM and CJS, anyways? Well, by definition, ESM stands for ECMAScript modules and CJS stands for Common JavaScript. ECMA is the governing body for JavaScript standardization and CJS, well…that’s kind of just the term that was given before any governing body had any say in the matter. Let’s first dive into some of the history surrounding these two methodologies

---

![Differences between ESM & CJS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xhdxdtk0sofygpu4nzvw.JPG)

By now, I’m sure we’ve all used both variations of these syntaxes to bring in some sort of code that we’ve needed within our existing codebase. CJS syntax uses the `require()` and `export.modules()` commands to either bring in and/or send out some bit of code we’ve either written ourselves or have used by someone else to make our code do what it’s supposed to do. 

Same exact thing for ESM syntax; it uses the `import` and `export` commands to either bring in or ship out some code we’ve either written ourselves or have used by someone else to make our code work. Seems like both are basically doing the same thing, right? So...

---

![What's the difference between ESM and CJS?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q3jr2e28gvx1fr3veot1.JPG)


Well, that’s my current job to explain to you, but just let me say that difference is literally EVERYTHING. Let's explore how these two seemingly comparative modules differ so greatly.

---

![CJS vs ESM methods](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mlse0oyxrajls7keimox.JPG)

At its core, CJS is **_synchronous_** and **_dynamic_** in its methods of importing and exporting modules. This means that a single module is only able to be loaded at a time and the next module is unable to be processed until the previous one has finished. The dynamic aspect comes into play when only certain modules are required in, as they’re needed at run-time. 

ESM on the other hand, is **_asynchronous_** and **_static_**; this means that modules are able to be loaded in, regardless of any current processes occurring. The static aspect of ESM pertains to the _analysis_ of a given module and its dependencies, once they’re compiled. 

Overall, both of these module implementations have their own strengths and weaknesses; the most common use-case for either of these, is for front end processes to use ESM and back end processes to use CJS. 

But how did we arrive at this point? And why are computer nerds everywhere choosing factions to represent their chosen side to fight the good fight? To answer that, we need to consult the dusty tomb of JavaScript’s history.

---

![ESM & CJS history](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdccj12g99aomc956p3v.JPG)

CJS was first introduced in the year 2009 by Mozilla engineer [Kevin Dangoor](https://www.kevindangoor.com/about/), after the need for the modularizing of code became evident for larger code bases. **_Imagine_** having to contain an entire full stack project within a single file of code; _NO THANK YOU!_ 

So with the mother of innovation fully at work, CJS was born to address the lack of a module system in JavaScript, primarily for server-side applications. And a fun fact about CJS is that it was initially called _ServerJS_.

So that’s great and all, but if CJS solved so many problems, why was there a need for ESM to exist in the first place? The answer to that lies within the need for a standardized approach to the existing module system, as well as the need for asynchronous functionality that front end based applications necessitate.
 
---

## ENTER: ESM


ESM was introduced in 2015, alongside the advent of ES6, which brought about significant changes that a lot of us take for granted as the baseline approach for operating within JavaScript. 

Ever since, the question on everyone’s mind has been, “why don’t we get rid of CJS and only implement ESM as the main source of truth for the modules we use?” 

…do you _want_ the truth? Do you _really want_ the truth?

## Well I don’t think you can _handle_ the truth! 

But alright, the truth is… 

---

![Legacy code and an unwillingness to change](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy0kzhh72469vdia9ovc.JPG)

What you see before your very eyes. That’s right my fellow friends, CJS solely exists because old engineers **_refuse_** to adapt to any kind of new technology and are firmly set in their tired and decrepit ways.

Alright, so not really, but based off of the digging around I’ve done, that seems to make up at _least_ 20% of the reason why. 

The bulk of the reason why these two approaches exist lies within the fact that Node.js has signed a blood-pact with CJS and isn’t functional without it, unless of course you bring in some external configurations to make server-side processes use ESM syntax, against their will (for example Typescript). 

It’s also worth it to note that the vast majority of browsers simply refuse to acknowledge CJS’s existence, unless you bring in some kind of transpiler to force that acknowledgement. Although, before I get too ahead of myself, let’s gaze into the opening statement I had first written out at the beginning of this post; insecurity vs. security: 

---

![insecurity vs. security](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30mna21wbcr4ytrg5yfz.JPG)

Back in 2016, a single individual disrupted the entire ecosystem of the web by deleting a mere 11 lines of code, as you see above. Sites that used React, such as Facebook, Instagram…basically any website you visit these days, experienced a downtime of about 2 hours thanks to this deletion of only _11 lines_ of code. 

Seems like nothing super impressive, right? 

**_Wrong._** 

The single fact that over half of the Internet’s most-visited sites experienced that long of a down time, and billions of dollars lost in ad revenue, speaks volumes to just how fragile of an ecosystem we’re operating within. This piece of code, called "**left-pad**", is a package found within npm which solely operates based off of CJS modules. 

The beauty and downfall of npm-based packages, and in turn, CJS modules, is that anyone is able to alter (or delete) their packages operating within CJS boundaries, and if the vast majority of websites are using that very code you’ve written, either knowingly or blissfully ignorant, significant changes can happen in the blink of an eye. 

I encourage you to [research](https://www.theregister.com/2016/03/23/npm_left_pad_chaos/) more into this event, to see the full brevity of this small change that disrupted countless operations. 

This isn’t to say that if this code had been using ESM modules that this could’ve been avoided, I only point this out to show that regardless of how much security we try to incorporate into the tools we use, we very much rely on external factors, even if we know it or not. 

So with that, let’s dive into how ESM and CJS incorporate their different versions of security:

---

![Pick your flavor of security](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mc0e7ou044lfkirbnr06.JPG)

**ESM** introduced a number of security enhancements, which I’ll touch on a few of them briefly. 

The first big implementation was an improved _**module-specific scoping**_, which means whoever is importing a package has to define their own variables, as opposed to having variables brought into the global scope. 

Another big security feature upgrade was the use of **_import maps_**. Import maps are aiming to replace the use of bundlers, such as webpack, by calling in the packages you need in the actual `<script>` tag in an HTML file. You simply link the URL within an `imports` property and this helps with the resolution of the module, as well as controlling the origin of where that module is coming from. Currently, only a single import map is allowed per document, but changes are in the pipeline to address this limitation. 

Now let’s get into the _extremely_ limited security features of the CJS module.

**CJS** offers a _little_ bit of _**encapsulation**_, but this is basically just from the encapsulation that JavaScript already offers natively from its scope within functions. So, not so much of a CJS-specific security implementation, it’s moreover a nice thing that happened to already be there. 

The next security feature that was, in fact, implemented specifically for CJS is our trusty friend `package-lock.json`. This concept was introduced by npm to ensure **_reproducibility_** within any given module, so that what you got was going to function as the creator intended.

 Although the beauty and terror within that statement is the latter; you’re at the behest of the creator. So by and large, most npm packages aren’t going to be malicious. But it only takes one bad actor to alter a single file and half of the Internet is down for an indeterminate amount of time. 

So with all of these competing factors...

---

![What's a dev to choose?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r9ai3d4z93n9llii1wla.JPG)

Should we choose **_team ESM_** for their improved security features and the slowly encroaching takeover that all modules, both back and front end, are eventually going to  succumb to? 

Should we choose **_Team CJS_** for their tried and true methods standing the test of time and the ease of use that a vast majority of npm packages are based on? 

Or should we all just go the **_nihilism route_** and finally realize either of these viewpoints are essentially meaningless? 

Well, allow me to give you a _100%_ objective and absolutely **_NOT_** biased take:

---

![It depends](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dqqiyc7p9un366q1vxe0.JPG)

Though it may seem like I’ve been framing either module systems as at odds against each other, the truth is that each system has their own use-case. 

The main take-away here is that, if you’re writing a new app from scratch, try to implement ESM if you can. ESM has significant security improvements, and just the fact that you only need to use a single type of syntax to import and export for both the front end and the back end, is a pretty cool thing.

CJS has been the de-facto standard for a very long time and is still widely implemented within the entire ecosystem that we all call home. The fact that it’s existed, and still is thriving for this long, stands as a testament to just how well it’s doing its job. So if you’re working within an existing full stack application and the back end is already using CJS, it’s probably fine to stick with it. Plus, you can also [convert it to ESM](https://pawelgrzybek.com/all-you-need-to-know-to-move-from-commonjs-to-ecmascript-modules-esm-in-node-js/) pretty effortlessly. 

Overall, both methods have their own use-cases and a single one isn’t necessarily better than the other and both will more than likely be around for a very long time.

---

And with that my friends, we’ve reached the end of this controversial talk. If there’s anything you take away from this, it’s that **_absolute power_** corrupts **_absolutely_** and ESM and CJS are friends, rather than foes.

Thank you for reading! ~~ **<3**