Transpiling dependencies with Babel

(Ad, please don’t block)

Figuring out the best strategy for transpiling with Babel is tricky. This blog post proposes a new strategy, made possible by Babel’s preset-env.

The vision  

For a while, the best approach for building web apps via webpack was:

  • App code: transpiled via Babel and preset-latest:
    • Input: JavaScript with stage 4 features or older, in ES modules.
    • Output: ES5 in CommonJS modules.
  • Dependencies: are already transpiled.

Then Babel introduced preset-env, which lets you adapt the code you generate to the platforms it will run on. Now, the best approach would be:

  • App code: transpiled via Babel and preset-env.
    • Input: JavaScript with stage 4 features or older, in ES modules.
    • Output: whatever JavaScript runs best on the target platforms.
  • Dependencies: must be delivered as both...
    • Transpiled code that runs natively on Node.js (meaning CommonJS modules, at the moment).
    • Untranspiled code that is transpiled along with your own code.

The benefits of this new approach are:

  • ES modules enable tree-shaking (which, in general, is impossible with CommonJS modules).
  • The output is upgraded automatically as the target platforms evolve (thanks to preset-env).
  • You can target multiple platforms.

Implementing the vision  

How should we deliver both transpiled and untranspiled code in npm packages? Three properties in package.json currently let you deliver multiple versions of the same code (for more information, consult “Setting up multi-platform npm packages”):

  • main: points to a CommonJS module (or UMD module) with JavaScript as modern as Node.js can currently handle.
  • browser: swaps out some of the main code so that it works in browsers.
  • module: the same code as main, but in ES modules.
  • es2015: untranspiled ES6 code. Introduced by Angular.

Alas, none of these properties help with delivering untranspiled code (beyond ES6). Therefore, I propose to use package.json as follows:

  • untranspiled: source code using stage 4 features (or older), not transpiled, in ES modules. Structured as a JSON object with two properties:
    • main: points to the untranspiled code.
    • browser: points to browser-specific untranspiled code.
  • main continues to be used the same way. Its code can be generated from the untranspiled code.
  • browser and module will become less relevant, because most of their uses cases can be handled via untranspiled.

This is an excerpt of a package.json using the new properties:

    "main": "main.js",
    "untranspiled": {
        "main": "main-latest.js",
        "browser": "browser-specific-main-latest.js"

Additionally, it may make sense to specify what features are required by the untranspiled code:

    "untranspiled": {
        "main": "main-latest.js",
        "uses": [

Other solutions is an abbreviation for “property foo in package.json”.

webpack can be configured to recognize alternatives to pkg.main (via resolve.mainFields). That enables you to refer to untranspiled code via pkg.module. Note that that is not exactly how module is supposed to be used, but it lets you use packages with that property. And the following solutions become possible:

  • webpack contributor Sean Larkin always transpiles all dependencies. This increases build times, but has the advantage of being easy to configure.
  • Jason Miller proposes to tell webpack to transpile only packages that have pkg.module, via clever use of module.loaders and include. He documented his approach in a Gist.
  • Sam Verschueren has written the Babel plugin babel-engine-plugin, which only transpiles packages that depend on Node.js versions newer than 0.10 (with features beyond ES5). To do so, it checks pkg.engines. This plugin could be adapted to support the new approach outlined in this blog post.

Yet another quick-and-dirty approach is to use file extensions:

  • .js is never transpiled.
  • .esm is always transpiled, even in npm-installed dependencies. It’s best not to use the upcoming file extension .mjs, so that future code doesn’t break packages using this approach.

This approach is especially handy if your own app is spread out across multiple npm packages.

Further reading  

Most popular (last 30 days)