Tomorrow’s ES Modules Today!

💡 For a deeper dive check out the Node Module Summit video.

Seven months ago I released an experimental module loader that enabled ES modules in Node. With over a million downloads, thousands of commits, and the tremendous help of early adopters and the JavaScript community, it’s ready for its stable release! 🎉🎉🎉

Meet esm – A fast, production ready, zero-dependency ES module loader for Node 6+ that delivers an awesome developer experience to boot!

As with its predecessor, developers can easily enable ES modules in many of their favorite tools by using the “require” options of each:

node -r esm
mocha -r esm
nyc -i esm
webpack -r esm

💡The AVA test runner can defer to esm for its ESM support.

In addition, package authors can now start new esm enabled packages using one of the following commands:

npx create-esm
yarn create esm
npm init esm (soon)

💡Use the -y flag to answer “yes” to all prompts.

npx create-esm used to initialize an esm enabled package

Setting the Stage

Before I dig into esm, let me set the stage with a bit of backstory.

I’ve written JavaScript for 20 years, participating in open source and reading specifications for 13. I’ve lived through the Web 2.0 days and did my part to contribute to the birth of modern JavaScript libraries and frameworks. I’ve seen what approaches and attitudes work in the JavaScript ecosystem and which ones fall flat or fizzle out.

I’ve been a technical program manager (PM) at Microsoft for 6 years. I was the performance PM for Chakra, Microsoft’s JavaScript engine, for 3 and I’m currently a Developer Experiences PM on the Web Platform Team. Program management requires empathizing with users and identifying user stories, feature requirements, external dependencies, and potential blockers. It requires working across teams, and even across companies, to see a feature from initial idea to shipped code.

I created the Lodash JavaScript utility library. Lodash began as fork of the Underscore library. Underscore started the same year as Node and quickly became its de facto utility package. Three years later I released Lodash and took it from absolutely nothing to becoming the most depended upon npm package with millions of users. To accomplish that feat required a mindset in which the practices and approaches of developers weren’t seen as a problem to be corrected. Instead of pushing against the ecosystem, I embraced their various module formats, programming styles, and myriad of configuration preferences. I unblocked developers and made migration painless. I improved performance-critical scenarios and simplified development. I cultivated trust and made something easy for developers to reach for.

These experiences have shaped how esm approaches compatibility and interoperability.

Zero Configuration

When esm was initially released, back in August 2017, Node was 9 months away from shipping ES modules unflagged. Now, 8 months later, Node has pushed back unflagging ES modules another 9-27 months, a new Node Modules Working Group has been formed (I’m a member), and the Node roadmap that once was isn’t necessarily what will be.

Defaulting to “Nodes rules” now, when those rules haven’t yet been ironed out, doesn’t seem like the right move. Developers should be able to pick up ESM and get coding fast. Ideally, ESM support should fall in line with where the ecosystem is today. With zero configuration esm should be as CommonJS (CJS) interoperable as possible. The things you ❤️, like application performance monitors, bundlers, code coverage instrumenters, transpilers, and type checkers, should continue to just work.

Node’s ESM plans still have the .mjs file extension as the default mechanism to signal the module parse goal. However, because Node’s .mjs story isn’t fully written, the JavaScript ecosystem, as seen with Babel 7 and Webpack 4, can only responsibly support bare minimum functionality for it. Following their lead, esm locks down .mjs disabling all esm options. This means that today, .js is your best bet for things to just work. esm is a bridge from the ESM of today to the ESM of tomorrow. As Node’s path to ESM becomes clear, esm will allow developers to step into it.

Mostly JavaScript is Mighty Good

You might think because esm isn’t totally native code, being mostly written in JavaScript, that it’s inherently inferior. However, being mostly JavaScript is actually a major strength. Adding support for ESM in Node requires various amounts of buy-in and work-from several external teams and standards bodies. All of whom have different agendas, priorities, and timelines. Trying to corral all parties together with a unified vision of Node’s ESM support is not practical. Being mostly JavaScript means esm needs zero buy-in or tie-in to these dependencies while ESM support is being ironed out, enabling faster development and greater ecosystem support for esm than other approaches. This not only unblocks esm, but also gives it the freedom to take advantage of native helpers, like vm.Module, as they become available. Writing builtin functionality in JavaScript is nothing new. Node’s own builtin modules and much of their in-progress ESM work is written in JavaScript. Even parts of JavaScript engines like Chakra, SpiderMonkey, and V8 are written in self-hosted JavaScript. Perhaps most exciting is that because esm is a standalone loader it can work across JavaScript engines, like ChakraCore, and be trivially compiled directly into Node!

Better than “Just works”

ES module syntax is nice to have, but it’s not critical in Node to get things done. The barrier for adoption has to be incredibly low, with clear benefits, or developers will stick with tried and true CJS. This means esm has to do more than enable instant and seamless ESM adoption.

  • Resilient 
    esm runs in its own context so it’s more resilient to prototype and polyfill tampering.
  • Reduce pain points
    esm smooths over API roughness across Node versions. For example, require.resolve and require.resolve.paths enhancements are in Node 8+, but are unavailable in Node 6. With esm you get access to these APIs, even in Node 6.
  • Improve performance
    esm can improve your application’s startup time by leveraging engine code caches that projects like Parcel, Webpack, Yarn, and many Electron applications benefit from today.

Powering-Up Developers

esm is all about removing artificial barriers to, and empowering developers with, modern syntax.

  • Full-stack
    With esm developers can write ESM for both server-side and client-side. For example, esm enhances server-side rendering of Svelte components and allows cross platform applications to be written entirely in ESM.
  • Real-time
    Quokka.js and Wallaby.js offer scratchpads and integrated testing for text editors, like VS code. Both projects use esm to enable support for real-time ESM syntax without jumping through additional hoops, using ESM-only loader hooks, juggling mime-types, or forking code bases. For Quokka.js, the integrated scratchpad, esm even supports parsing unsaved documents without file extensions!

Pay it forward

While developing esm, I’ve been on the lookout for opportunities to positively impact others. The following are a few examples of this.

  • Babylon was patched to enable parsing and linting of top-level await.
  • Several Proxy issues in Chakra, V8, JavaScriptCore, & SpiderMonkey were uncovered while developing support for named exports of CJS modules.
  • Node was patched to ensure --check and --require options work together.
  • The work in progress pull request to support named exports of builtin Node modules is based on esm’s early implementation.
  • The npm ES modules proposal, which spurred the creation of the Node Modules Working Group, took esm as inspiration that alternative implementations were possible.

What’s Next

Even though esm is celebrating its stable release there’s still plenty of things to come.

  • The foundation for loader hooks has been laid in esm so, when things solidify in Node, esm will follow suit.
  • I’m tasked with creating a Node tooling benchmark to help JavaScript engines track Node centric performance of bundlers, instrumenters, loaders, and utilities.
  • I’m teaming up with npm to enable npm init <create-pkg-name>!
  • Experimental WASM support will land soon. Using the wasm option you’ll be able unlock loading .wasm files in CJS and ESM!

I’ve seen the delight of developers when their ESM code just works and hope esm can serve as a reference implementation and powerful example of what is possible!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.