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:
npm init esm (npm@6)
yarn create esm
💡Use the -y
flag to answer “yes” to all prompts.
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 tentatively, 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
andrequire.resolve.paths
enhancements are in Node 8+, but are unavailable in Node 6. Withesm
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
Withesm
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 useesm
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, tookesm
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 enablenpm 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!