An ESM bundle for any NPM package

Joel Denning
4 min readFeb 5, 2020

--

Short version — some npm packages now have a corresponding package under the @esm-bundle scope

If you’re looking for an up-to-date, published version of an npm package in ESM (javascript module) or System.register format, search for it on https://github.com/esm-bundle or on npm at https://www.npmjs.com/search?q=%40esm-bundle.

If the package isn’t there, you can create a repository that auto-publishes an ESM and SystemJS version every time the official version publishes. To do so, follow these instructions.

Example:

Long version — we need this as a javascript community

Modules have been part of javascript since 2015, but are still not (usually) used in production systems. Here are reasons why that may start changing:

  1. Webpack will soon support compiling to ESM in a 5.x release.
  2. Browsers may soon support aliasing an import specifier to a URL. (import maps are implemented in Chrome behind a feature flag).
  3. Cross site caching of javascript assets is rather performant. (Edit: double key cache nullifies this benefit).

For early adopters and enthusiasts, projects like import-map-service-worker, snowpack, and es-module-shims are a few projects to acquaint yourselves with.

But bundles are bad because…reasons

Let’s start by debunking some myths:

Myth: you can’t tree shake a bundle.

This is just false.

Myth: one day we won’t need bundlers

Without a bundler, every source file results in a network request. Us developers create too many files for that to be performant. No, not even HTTP push is going to save us here.

Also, cross-browser compatibility isn’t ever going to go away. Yes, we’ve mostly escaped IE and are often only supporting evergreen browsers. But we still have to tell terser when we want to work around weird safari 10 bugs. And we tell babel exactly which browsers we want to support so that it can employ its encyclopedic knowledge of browser features to make sure that things work.

Myth: publishing individual ESM files is good enough

This is subjective, but in my opinion a bundle that you can import() in the browser from a CDN is way easier to use in production than dozens of source files that might need to be compiled and concatenated before production use.

Myth: Glueing things together in the browser is really bad for performance when compared to build-time.

Although there’s something to be said about optimizations that can only occur when everything is processed at build time (dead code elimination / tree shaking), there’s also something to be said for caching really common libraries like React and RxJS across all the websites you visit. (Edit: double keyed HTTP cache nullifies this effect)

Also, common libraries like React often are not benefiting from tree shaking at all, anyway, due to webpack’s inability to tree shake CommonJS modules.

Finally, a huge monolithic build often doesn’t actually result in better bundle sizes, due to the expertise required to manage it.

Fact: bundling a published package means that the publisher chooses the browser target instead of the user of the package.

This one is true. Here is a good article from Jason Miller (@developit) about this.

Approach — don’t wait for PRs or permission

Since many open source projects don’t have the time, desire, or people to make and publish an ESM and System.register bundle for their library, I took the liberty of doing it myself.

The React library is a good example — their rollup config is so nasty (yay monorepos!) that even the smartest of contributors are struggling through WIP PRs for an ESM react.

I’ve found it is often easier just to take the published npm artifacts and run them through rollup myself. It’s not because the open source maintainers are doing a bad job — it’s just that it’s hard to always be on top of everything.

I decided to publish them under the @esm-bundle npm scope, so that they are available via yarn add thing@npm:@esm-bundle/thing and on CDNs at unpkg and jsdelivr.

Nuts and bolts — autopublishing @esm-bundle packages

To make this whole thing worthwhile, we need these unofficial ESM versions to be up-to-date. Trying to do this manually is not scalable beyond a couple projects.

So I decided to make the ESM versions auto publish every time the official version is published.

There were five parts to it:

  1. Use rollup on the official package that is located inside of node_modules.
  2. Use a bot to automatically update the official package whenever a new version is published.
  3. Use a bot to automatically merge the pull request.
  4. Set up CI to automatically bump and publish the @esm-bundle package.
  5. Set up extensive, but reusable, tests that verify the libraries can actually be downloaded as ES modules in a real browser.

Managing an ecosystem

I don’t know if this project will catch on, but if it does I wanted to be ready for as many outside contributors as possible to create and maintain esm-bundle repositories.

To that end, I created these instructions for how to contribute to a new esm-bundle repo. Contributors will use this Github template repo to start off. Once they have it working locally, they can ask for Github permissions so they can transfer the repo into the esm-bundle organization. Once transferred, CI environment variables for publishing to npm will be granted.

Is 2020 really when ES modules will finally be used?

I don’t know, but if so let’s be ready for it!

--

--