Skip to main content
Progress of JavaScript being obstructed by CommonJS.

CommonJS is hurting JavaScript

JavaScript, the undisputed king of web development, is being sabotaged — not by a rival language or a revolutionary new technology, but by its own baggage from the past. This insidious saboteur is none other than CommonJS, the antique module system that we’ve tolerated for far too long.

The rise of CommonJS

About 15 years after its invention, JavaScript started expanding beyond the browser to the server. Bigger projects were being built with the language and JavaScript needed a better way to handle a lot of source code. It needed modularization.

In 2009, Mozilla developer Kevin Dangoor made a call to arms. In “What Server Side JavaScript needs”, he laid out a lot of what the nascent field of server side JS was missing, including a module system.

JavaScript needs a standard way to include other modules and for those modules to live in discreet namespaces. There are easy ways to do namespaces, but there’s no standard programmatic way to load a module (once!). This is really important, because server side apps can include a lot of code and will likely mix and match parts that meet those standard interfaces.

— Kevin Dangoor, What Server Side JavaScript needs (2009)

Within a week, 224 people had joined the then-called ServerJS google group, including npm founder Issac Schlueter, and Node.js creator Ryan Dahl (here’s where he introduces Node to the group). This mailing list would go on to spec out the first versions of CommonJS, the module system that became part of Node.

The proposed CommonJS syntax (require(), module.exports, etc.) do not look like client-side JavaScript. This was by design. Dangoor’s intention to distance CommonJS from browser JavaScript was made clear from his message in the CommonJS Google Group in 2009:

I really think that the needs of server side code are different enough than the needs of client side code that we’re better off drawing from Python and Ruby than from Dojo and jQuery.

Beyond Node.js several other early server-side JavaScript runtimes adopted CommonJS, like Flusspferd, GPSEE, Narwhal, Persevere, RingoJS, Sproutcore, and v8cgi (most built by the core CommonJS group).

But as Node.js became the de-facto server-side JavaScript runtime with CommonJS as its main module system, the wider CommonJS standardization efforts lost steam. There were fewer needs for standards when there was only one main runtime: Node.js implementation became the standard.

In retrospect, it seems to me like the goal of CommonJS was (or at least, should have been) to discover Node, and enable what we’ve built here. Some mistakes were made, because hindsight doesn’t go in the direction you’d like it to, but overall, I think the whole CommonJS project can be considered a success.

— Issac Schlueter, comment on Breaking the CommonJS standardization impasse (2013)

Despite being the default module system, CommonJS have some core problems:

  • module loading is synchronous. Each module is loaded and executed one by one, in the order that they are required.
  • difficult to tree-shake, which can remove unused modules and minimize bundle size.
  • not browser native. You need bundlers and transpilers to make all of this code work client-side. With CommonJS, you are stuck with either big build steps or writing separate code for client and server.

By 2013, the CommonJS group was starting to wind down. But by that year, The TC39 committee that oversaw updates to the core JavaScript language were already working on a successor to CommonJS modules: ECMAScript modules.

ECMAScript Modules are web-first

With the ES6 language spec, the TC39 committee finally introduced a module system built right into the JavaScript language. The goal is to build a single module loader system that is suitable for the web, which includes asynchronous module loading, compatibility with browsers, static analysis, and tree shaking.

ES modules assume they’ll fetch data over a network rather than in a filesystem, delivering better performance and user experiences.

Now that a module loading system is built right into the language, everyone will agree to use that so we can focus our energy on higher level, more important problems, right?

Anakin Padme 4 panel meme about ESM vs. CommonJS

…right?

Node decides to support both CJS and ESM

ES Modules and Common JS go together like old bay seasoning and vanilla ice cream

— Myles Borins, from a talk on Modules Modules Modules

(I’m from Maryland so this sounds great to me.)

Borins was one of the developers on the Node ‘modules team’ tasked with implementing ES modules in Node. Despite successfully adding ESM to Node, the team couldn’t come to a clear consensus on interoperability between ESM and CJS. Yet Node couldn’t rip out CJS since its so deeply embedded. This meant that the interoperability problem was pushed to package authors.

Here’s a snippet of a module’s package.json needed to support both ESM and CJS:

Publishing a module to support esm and cjs “publishing packages that work in esm and cjs is such a nightmare” — Wes Bos

Other module authors have found success supporting CommonJS and ESM using dnt. Simply write your module in TypeScript and this build tool will transform it to Node.js, emit ESM/CommonJS/TypeScript declaration files, and a package.json.

It’s clear that supporting CommonJS in 2023 has become too big of a problem to ignore. It’s time we bury CommonJS and transition into an all ESM future.

So long and thanks for all the requires

We envision a future where after installing a module, developers will be able to run the code in Node.js or the Browser without requiring a build step.

— Myles Borins, The Current State of Implementation and Planning for ESModules (2017)

In 2009, CommonJS was exactly what JavaScript needed. The group took a tough problem and forced through a solution that continues to be used millions of times a day.

But with ESM as the standard and the focus shifting towards cloud primitives — the edge, browsers, and serverless compute — and CommonJS simply doesn’t cut it. ESM is a better solution for developers, as they can write browser-compliant code — and for users who get a better end experience.

Join the discussion.

Don’t miss any updates — follow us on Twitter.