What you need to know about ES modules in Node.js
Tags: node, esm, ecmascriptmodules
Published: 2021-01-18 12:03:26

> ECMAScript modules are the official standard format to package JavaScript code for reuse.

_— Source: [Node.js documentation for ECMAScript modules](https://nodejs.org/api/esm.html)_

Up until recently I've tended to avoid using ES modules and `import/export` syntax in Node.js. I've found it hard to keep track of which versions of Node.js support this syntax out-of-the-box, as well as which ES module features they support. I've put together this short guide in the hope that I can help clear up any confusion that other folks might also have around ES modules in Node.js.

This blog post isn't going to cover much about what ECMAScript (ES) modules are or how to use them. There are lots of resources out there that already do a great job of this. If you want to learn more about ES modules, check out [my top links for learning more](#my-top-links-for-learning-more) at the end of this post.

As the quote from the Node.js documentation above mentions, ECMAScript modules are the official [standard format](https://tc39.github.io/ecma262/#sec-modules) for modules in JavaScript. The browser JavaScript ecosystem (tooling and browsers) has had good support for ES modules for quite a while now. Meanwhile, there have been tremendous efforts over the past few years to bring ES module support to Node.js too, which has historically only used the [CommonJS module format](https://nodejs.org/api/modules.html).

Let's take a look at what you need to know about ES modules in Node.js today.

> I intend to update and expand this blog post as needed. If you spot anything incorrect, out-of-date, or which you feel is missing from this post, please [drop me a message on Twitter](https://twitter.com/simonplend). I want to make sure this is a useful and accurate resource!

## Jump links

- [Several names for the same thing](#several-names-for-the-same-thing)
- [Which versions of Node.js have full support for ES modules?](#which-versions-of-nodejs-have-full-support-for-es-modules)
- [Why are ECMAScript modules marked as 'Experimental' in the Node.js documentation?](#why-are-ecmascript-modules-marked-as-experimental-in-the-nodejs-documentation)
- [ES and CommonJS modules play nice together (mostly)](#es-and-commonjs-modules-play-nice-together-mostly)
- [Use ES modules when you start a new Node.js project](#use-es-modules-when-you-start-a-new-nodejs-project)
- [It's probably not worth migrating your existing Node.js applications to use ES modules](#its-probably-not-worth-migrating-your-existing-nodejs-applications-to-use-es-modules)
- [Potential pain points](#potential-pain-points)
  - [Limited test library support for mocking ES modules](#limited-test-library-support-for-mocking-es-modules)
  - [You can't import JSON files without using an experimental flag](#you-cant-import-json-files-without-using-an-experimental-flag)
- [My top links for learning more](#my-top-links-for-learning-more)

## Several names for the same thing

It took me a while to understand that there are several names which refer to the same thing. In this blog post I'm referring to them as "ES modules", but they are more generally known as "JavaScript modules". This is because they are a language-level module syntax for JavaScript (hence the standard I referenced earlier).

In the Node.js documentation you will generally see JavaScript modules referred to as "ECMAScript modules". In the wider world, this is often abbreviated to "ES modules", or simply "ESM". These are all a reference to **the same thing**.

```
JavaScript modules = ECMAScript modules = ES modules = ESM
```

## Which versions of Node.js have full support for ES modules?

ES module support was [added to Node.js in v8.5.0](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md#8.5.0) (yes, really!), and released in September 2017. At that stage it was highly experimental and missing a lot of the ES module features which Node.js now supports.

Node.js now has full support for ES modules and you can happily use them in Node.js 12.x versions and above. I would however recommend that you use newer versions of Node.js 12.x and 14.x as they have have more mature and stable ES module support. Importantly, they also have improved interoperability with older style CommonJS modules, which are still widely used. I explain this in more detail in my article '[Node.js now supports named imports from CommonJS modules, but what does that mean?](https://simonplend.com/node-js-now-supports-named-imports-from-commonjs-modules-but-what-does-that-mean/)'.

Here are the versions I recommend using for the [LTS release lines of Node.js](https://nodejs.org/en/about/releases/):

- **14.x — v14.13.0 or higher**
  - _Why? Support for detection of CommonJS named exports, unflagged [top-level await](https://nodejs.org/docs/latest-v14.x/api/esm.html#esm_top_level_await)._
- **12.x — v12.20.0 or higher**
  - _Why? Support for detection of CommonJS named exports, Loading ECMAScript modules no longer requires a command-line flag._
- **10.x — Don't use ES modules in Node.js v10.x releases**
  - _Why? It's missing some key ES module features. It's also end-of-life on 2021-04-30, meaning this release line  will no longer receive bug fixes after that date._

It's not a deal breaker - you can use v12.0.0 or v14.0.0 if you like, but using the versions I've mentioned above (or higher) will make your life easier.

## Why are ECMAScript modules marked as 'Experimental' in the Node.js documentation?

_"Hold up, Simon, the Node.js 12.x and 14.x documentation for ECMAScript modules says that this feature is Experimental!"_

ECMAScript modules are marked as 'Stable' in the [Node.js 15.x documentation for ECMAScript modules](https://nodejs.org/docs/latest-v15.x/api/esm.html), however in the [12.x](https://nodejs.org/docs/latest-v12.x/api/esm.html) and [14.x](https://nodejs.org/docs/latest-v14.x/api/esm.html) documentation they are marked as 'Experimental'.

If Node.js 12.x and 14.x releases have full support for ES modules, what gives? I was wondering the same, so I [asked Matteo Collina on Twitter](https://twitter.com/simonplend/status/1348995209884016640) (he's a member of the [Node.js TSC](https://github.com/nodejs/TSC)). Myles Borins (also a member of the TSC) chimed in on the thread to explain the rationale behind ES modules being marked as 'Experimental' in the 12.x and 14.x release lines:

> We've explicitly not marked it as stable yet just in case we need to make breaking changes between 15 -> 16 that we may want to backport. I don't think 12 will ever get marked stable due to being maintenance.

_([Source tweet](https://twitter.com/MylesBorins/status/1349016345195786241))_

> If we are ready to make the commitment of not breaking anything then yes, it is stable. The 0th hour deprecation of subpath folders does leave me pause though. Also keep in mind that for LTS being experimental makes it easier to make non-breaking changes too.

_([Source tweet](https://twitter.com/MylesBorins/status/1349021538163585025))_

My interpretation of this is: you can safely go ahead and use ES modules in Node.js 12.x or 14.x, as the core implementation is highly unlikely to change at this stage. Keeping ES modules marked as 'Experimental' in these Node.js release lines gives the developers working on Node.js some wiggle room to change things if needed. On an open source project of this scale, that makes a lot of sense to me.

Thanks to [Matteo](https://twitter.com/matteocollina) and [Myles](https://twitter.com/MylesBorins) for helping clarify the situation.

## ES and CommonJS modules play nice together (mostly)

Given that the npm registry lists almost 1.5 million packages, many of which _only_ expose a CommonJS module, it's safe to say that interoperability between the ES and CommonJS module formats is really important. The good news is that they generally work pretty well together.

You can reliably `import` a CommonJS module in an ES module e.g.

```javascript
import someModule from "someModule";
```

Thanks to some [amazing work](https://github.com/nodejs/node/pull/35249) by [Guy Bedford](https://twitter.com/guybedford/), [Geoffrey Booth](https://github.com/GeoffreyBooth), and host of other contributors, you can also import named exports from CommonJS modules (in most cases - I've [written about this in detail](https://simonplend.com/node-js-now-supports-named-imports-from-commonjs-modules-but-what-does-that-mean/)) e.g.

```javascript
import { someFunction, someObject } from "someModule";
```

What all of this means is that, in general, you can `import` CommonJS modules in your ES modules and Node.js will take care of all the module format interoperability for you. I think that's pretty damn impressive.

The Node.js ECMAScript module documentation has a section which covers all the details about [Interoperability with CommonJS](https://nodejs.org/api/esm.html#esm_interoperability_with_commonjs).

## Use ES modules when you start a new Node.js project

A new Node.js project is a great opportunity to start using ES modules. However, Node.js treats JavaScript code as CommonJS modules by default. This means you must tell it when it should treat JavaScript code as ES modules.

If all of your project's code will be in ES modules you can add `"type": "module"` in the project's `package.json` file and you're good to go. Node.js will treat every module in your project as an ES module.

Note: This doesn't mean the dependencies your project uses must use ES modules. ES and CommonJS modules have good interoperability, as explained above.

If only specific modules in your project will be ES modules, give the files they're contained in an `.mjs` extension (instead of `.js`) and Node.js will use the ES module loader for those scripts.

To learn more about configuring ES modules in Node.js I recommend reading the [Node.js Packages documentation](https://nodejs.org/api/packages.html#packages_determining_module_system) on this subject.

## It's probably not worth migrating your existing Node.js applications to use ES modules

There's no single hugely obvious benefit to migrating an existing Node.js application from using CommonJS modules to ES modules. Unless you have a good reason to, migrating to ES modules just for the sake of it is probably not worth it. Given that modules are imported when an application starts, you're not going to see performance improvements when your application is running e.g. handling requests and serving responses.

If you're working with a small application, by all means go ahead and migrate it to use ES modules for the learning experience. For large applications though, there are almost certainly other things you could better spend your time / money on (and it's likely that your employer or clients will agree).

Node.js packages _should_ potentially be updated to use ES modules. They can be changed to only use ES modules, or they can be configured to offer both CommonJS and ES modules (a "dual" package). The Node.js documentation dives into the [pros and cons of the "dual" package approach](https://nodejs.org/api/packages.html#packages_dual_commonjs_es_module_packages).

Sindre Sorhus, author of over 1,000 Node.js packages, is planning to convert all of their packages to only use ES modules in order to ["rip off the bandaid and push the ecosystem forward"](https://blog.sindresorhus.com/get-ready-for-esm-aa53530b3f77).

## Potential pain points

While Node.js support for ES modules is really good, there are a couple of potential pain points you should be aware of. I expect these potential pain points to go away over time as the wider Node.js ecosystem support for ES modules improves.

### Limited test library support for mocking ES modules

Jest and Sinon don't yet support mocking ES modules:

- Jest has [experimental support for ES modules](https://jestjs.io/docs/en/ecmascript-modules).
- Sinon does not have support for ES modules, but there is a recently re-opened [GitHub issue](https://github.com/sinonjs/sinon/issues/1832) which offers a workaround.

If you need an alternative for mocking, [testdouble.js](https://github.com/testdouble/testdouble.js/) has full ES module support thanks to [some excellent work by Gil Tayar](https://dev.to/giltayar/mock-all-you-want-supporting-es-modules-in-the-testdouble-js-mocking-library-3gh1).

The `testdouble` library is test-framework agnostic, meaning you can happily use it with any framework you're already using e.g. Mocha, Jest, Jasmine, Tape.

### You can't import JSON files without using an experimental flag

 You can't `import` JSON files - e.g. `some-file.json` - without using an experimental flag when you run `node`. This isn't a big deal for many applications, but if you're currently doing `require("some-file.json")` in your application, this is something to be aware of.

The Node.js documentation details [a workaround for loading JSON files in ES modules](https://nodejs.org/api/esm.html#esm_no_json_module_loading). You can also enable the [experimental JSON module loading](https://nodejs.org/api/esm.html#esm_json_modules) if you really need to.

## My top links for learning more

- [Node.js documentation for ECMAScript modules](https://nodejs.org/api/esm.html) - The latest Node.js documentation for ES modules. It's really good and very thorough.
- [MDN Guide on JavaScript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) - This is a fantastic guide which provides some useful background on modules in JavaScript, along with a wealth of practical JavaScript module examples. I ❤️ MDN so much.
- [NodeConf Remote 2020 - Gil Tayar - ES Modules in Node.JS](https://www.youtube.com/watch?v=mMEKrHmZLpQ) - An excellent introduction to ES modules in Node.js ([companion code + link to slides](https://github.com/giltayar/ah-theyre-here-esm-nodejs), [my notes from this talk](https://simonplend.com/notes-from-nodeconf-remote-2020/#talk-es-modules-in-nodejs)).
- [Node Modules at War: Why CommonJS and ES Modules Can’t Get Along](https://redfin.engineering/node-modules-at-war-why-commonjs-and-es-modules-cant-get-along-9617135eeca1) - This blog post is now a little bit out of date - e.g. ES modules can now import named CommonJS exports from many packages - but it's still well worth a read. When I read this article last year it really helped me understand the differences between the two module types and made a lot of things click for me.