The state of JavaScript modules

Johannes Ewald
May 23, 2017 · 10 min read
ESM, CJS, UMD, AMD — which one should I pick?

An ancient fear

Most frontend developers still remember the dark days of JavaScript dependency management. Back then, you would copy and paste a library into a vendor folder, rely on global variables, try to concat everything in the right order and still would had to deal with namespace issues.

Current implementations

There are currently three implementations of ESM in the wild:

  • module for modular code with explicit imports and exports

ESM in browsers

As of May 2017, all major browsers have shipped a working implementation of ESM. Most of them are still behind a flag, though. I won’t go much into detail because Jake Archibald has already written a great article about it.

<script type="module" src="main.js"></script>
// Supported:
import {foo} from 'https://jakearchibald.com/utils/bar.js';
import {foo} from '/utils/bar.js';
import {foo} from './bar.js';
import {foo} from '../bar.js';

// Not supported:
import {foo} from 'bar.js';
import {foo} from 'utils/bar.js';// Example from https://jakearchibald.com/2017/es-modules-in-browsers/

ESM with webpack

Build tools like webpack usually try to parse the code with the module mode. If anything goes wrong, they fall back to script. The result of these tools is a script and usually a module runtime which simulates both the behavior of CJS and ESM to a certain degree.

Simplified webpack output which simulates the behavior of ES modules
console.log(this);
“use strict”;console.log(undefined);
(function(module, exports) {

console.log(this);

})

ESM in Node.js

Node.js is having troubles implementing ESM because it still needs to support CJS. The syntax looks similar, but the runtime behavior is entirely different. James M Snell, member of the Node.js Core Technical Committee (CTC), has written an excellent article that explains the differences between CJS and ESM.

  • Exports are only known after evaluating the module
  • Exports can be added, replaced and removed even after the module has initialized
  • Imports and exports are linked before evaluating the module
  • Imports and exports are immutable
Simplified function wrapper around CommonJS modules in Node.js
  • both module systems must work simultaneously and as seamlessly as possible

The current trade-offs

In March 2017, after months of discussions, the CTC finally found a way to make that happen. Since seamless integration was not possible without changes to the ES specification and engines, the CTC decided to start with an implementation that comes with some trade-offs:

// CJS
const { a, b } = require("c");// ESM
import { a, b } from "c";

Will *.mjs be the Python 3 of Node.js?

With all these constraints in mind, one might ask what damage this transition will cause to the ecosystem. Although the CTC has worked hard to iron out the rough spots, there is still a lot of uncertainty regarding how the community will adopt it. This uncertainty is underscored by the claim by well-known NPM module authors that they will never use *.mjs in their modules.

Python 3 is killing Python

1. Strict backwards compatibility with CJS

Module authors that are not comfortable with ESM can still stick to CJS without being excluded. Their own code will not be affected by the adoption of ESM which reduces the likelihood of them moving to another runtime. It also eases the transition which can take some time in an ecosystem of NPM’s size. Refactoring from CJS to ESM puts a burden on package maintainers and I don’t expect all of them to have time for this.

2. Seamless integration of CJS in ESM

Importing a CJS module from ESM is pretty straight-forward. All you need to remember is that CJS exports only a single default value. Once you’re inside an ESM, you probably won’t even notice what module style your dependencies are using. Compare that with await import() from CJS.

What does this all mean for me?

With all the recent events, it is easy to become confused by all the options and constraints. In the following section, I’ve compiled typical questions that developers will face and my answers for them:

Do I need to refactor my existing code now?

No. Node.js has just started to implement ESM and there is still a lot of work to be done. James M Snell expects that it will still take a year at least and there’s still room for changes, so it is not safe to refactor now.

Should I use ESMs in my new code?

  • If you already have a build step like a webpack build or if you’re comfortable with having one, yes. It will ease the transition of your codebase and makes tree shaking possible. But beware: you will probably need to refactor some parts of it once Node.js has native ESM support.
  • If you’re writing a library, yes. Users of your module will benefit from tree shaking.
  • If you do not want to have a build step or if you’re writing a Node.js app, stick to CJS.

Should I use .mjs for ESMs now?

No. There’s currently no benefit of it and tooling support is still weak. I recommend to start the transition as soon as native ESM support lands in Node.js. Remember that browsers do only care about MIME types, not file extension.

Should I care about native browser compatibility?

Yes, to some extent. You should not omit the .js extension in your import statements anymore because browsers need full URLs. They will not be able to perform a path lookup like Node.js does. Similarly, you should avoid index.js files. However, I don’t expect that people will start to use NPM packages in the browser anytime soon because bare imports are still not possible.

What should I ship as a library author?

Write ESM and use Rollup or webpack to transpile it down to a single CJS module. Point the main field inside your package.json to this CJS bundle. Additionally, use the module field to point to your original ESMs. If you’re using new language features besides ESM, you should compile it down to ES5 and provide both a CJS and an ESM bundle. This way, users of your library can still profit from tree shaking while not having to transpile your code.

Look at all these tree shaken modules!

Summary

There is a lot of uncertainty concerning ES modules. Because of the trade-offs made by the current Node.js implementation, developers are afraid that it might disrupt the Node.js ecosystem.

webpack

The official Medium publication for the webpack open source project!

Johannes Ewald

Written by

flexbox all the things

webpack

webpack

The official Medium publication for the webpack open source project!