Supporting multiple Javascript environments



This content originally appeared on DEV Community and was authored by Evan K

I’ve been working on a small utility that I’ve recently published to npm, and my goal with it was to target both a Node.js and a browser environment.

In order to accomplish this, I picked up a tool that I’ve been loathe to touch since the last time I used it, roughly a decade ago — Babel.

What’s wrong with Babel?

While I wouldn’t actually say there’s something wrong with Babel, it does (necessarily) involve lots of complexity. It can be an absolute nightmare to configure, depending on your build process(es).

For this relatively simple project, within the space of a week and a half, I wrote, then threw away and rewrote my babel build configuration several times until I was satisfied with the end result.

Then why use a transpiler at all?

When used correctly, a build process that involves transpilation can strike the sweet spot where you’re able to use the latest ECMAScript features while maintaining backward compatibility with a variety of target environments.

In my case, I wanted to be able to write my source code using ES Modules that enjoy widespread browser support, while compiling down to the CommonJS module system that’s still widely used in Node.js.

Show, don’t tell

Full disclaimer: while this is the result of a lot of trial and error, I don’t claim to have met any standard of best practices, and would recommend you do your own homework. Decide what best meets your needs, rather than just following my (or anyone else’s) example.

Start to finish, my build process consists of a some nested npm run scripts and a decent amount of bash:

  • dist kicks off additional run scripts per build target
    • build-browser prepares the browser distributable by way of…
      • concat-browser concatenates a few source files together into a single build-main-browser.mjs script
      • transpile-browser runs babel on said script with the .babelrc.esm.json config and an output filename
    • build-node prepares the node distributable by way of…
      • concat-node concatenates a few source files together into a single build-main-node.mjs script
      • transpile-node runs babel on said script with the .babelrc.cjs.json config and an output filename
      • bash snippet that copies import.mjs for ESM export/import support on top of the transpiled CJS

This produces:

  • a browser targeted script in lib/ with the original ES Module exports, leveraging a browserslist query for which browser features to transpile support for
  • a Node v20 targeted script in dist/ transpiled down to CommonJS, with an additional script to tack on ES Module exports

You can see the result on npm, if you’re interested.


This content originally appeared on DEV Community and was authored by Evan K