ES Modules in Node Today!

👉 Update: The “@std/esm” loader is now “esm”. Read more here.

@std/esm used in the Node REPL after `npm --save @std/esm`

A tale of two module formats

With ESM landing in browsers, attention is turning to Node’s future ESM support. Unlike browsers, which have an out-of-band parse goal signal and no prior module format, support for ESM in Node is a bit more…prickly. Node’s legacy module format, a CommonJS (CJS) variant, is a big reason for Node’s popularity, but CJS also complicates Node’s future ESM support. As a refresher, let’s look at an example of both module syntaxes.

const a = require("./a")
module.exports = { a, b: 2 }
import a from "./a"
export default { a, b: 2 }
  • Go all in, shipping only ESM, and alienate users of older Node versions
  • Wait until 2020, after Node 8 support ends, to go all in
  • Ship ESM and transpiled CJS sources, inflating package size and shouldering the responsibility of ensuring 1:1 behavior

Bridge building

Enter the @std/esm loader, a user-land package designed to bridge the module gap. Since Node supports most ES2015 features, @std/esm is free to focus solely on enabling ESM.

  • Not polluting stack traces
  • Working with your existing tools like Babel and webpack.
  • Playing well with other loaders like babel-register
    (using .babelrc "modules":false)
  • Only processing files of packages that opt-in with a @std/esm configuration object or @std/esm as a dependency, dev dependency, or peer dependency in their package.json
  • Supporting versioning
    (i.e. package “A” can depend on one version of @std/esm and package “B” on another)
  • Only process what is used, when it’s used
  • The same code is executed in all Node versions
  • Features are configurable by module consumers
    (e.g. module “A” consumes module “C” with the default@std/esm config while module “B” consumes module “C” with cjs compat rules enabled)
  • More compliance opportunities
    (i.e. @std/esm can enforce Node’s ESM rules for environment variables, error codes, path protocol and resolution, etc.)

Standard features

Defaults are important. The @std/esm loader strives to be as spec-compliant as possible while following Node’s ESM rules.


Developers have strong opinions on just about everything. To accommodate, @std/esm allows unlocking extra features with the "@std/esm" package.json field. Options include:

  • Enabling unambiguous module support (i.e. files with at least an import, export, or "use module" pragma are treated as ESM)
  • Supporting named exports of CJS modules
  • Top-level await in main modules


Before I continue let me qualify the following section:

  • Loading CJS equivs was ~0.28 milliseconds per module
  • Loading built-in ESM was ~0.51 milliseconds per module
  • First @std/esm no cache run was ~1.56 milliseconds per module
  • Secondary @std/esm cached runs were ~0.54 milliseconds per module

Getting started

Run npm i esm in your app or package directory.

  1. Enable esm for packages:
    Use esm to load the main ES module and export it as CommonJS.
// Set options as a parameter, environment variable, or rc file.
require = require("esm")(module/*, options*/)
module.exports = require("./main.js")
// ESM syntax is supported.
export {}
node -r esm main.js

Meteor’s might

The @std/esm loader wouldn’t exist without Ben Newman, creator of the Reify compiler from which @std/esm is forked. He’s proven the loader implementation in production at Meteor, since May 2016, in tens of thousands of Meteor apps!

All green thumbs

Even though @std/esm has just been released it’s already had a positive impact on several related projects by:

What’s next

Like many developers, I want ES modules yesterday. I plan to use @std/esm in Lodash 5 to not only transition to ESM but also leverage features like gzip module support to greatly reduce its package size.

Final Thought

While this is not a Microsoft release, we’re proud to have a growing number of core contributors to fundamental JavaScript frameworks, libraries, and utilities at Microsoft. Contributors like Maggie Pint of Moment.js, Matthew Podwysocki of ReactiveX, Nolan Lawson of PouchDB, Patrick Kettner of Modernizr, Rob Eisenberg of Aurelia, Sean Larkin of webpack, and Tom Dale of Ember, to name a few, who in addition to their roles at Microsoft, are helping shape the future of JavaScript and the web at large through standards engagement and ecosystem outreach. I’m happy to guest post this news on the Microsoft Edge blog to share our enthusiasm with the community!



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
John-David Dalton

JavaScript tinkerer, bug fixer, & benchmark runner • Creator of lodash • Former Chakra Perf PM • Current Web Apps & Frameworks PM @Microsoft. Opinions are mine.