This post is part of a series of three:
- Current approaches: “Setting up multi-platform npm packages”
- Motivating a new approach: “Transpiling dependencies with Babel”
- Implementing the new approach: “Delivering untranspiled source code via npm”
Figuring out the best strategy for transpiling with Babel is tricky. This blog post proposes a new strategy, made possible by Babel’s
For a while, the best approach for building web apps via webpack was:
- App code: transpiled via Babel and
- 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
- 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
- You can target multiple platforms.
Implementing the vision
How can we deliver both transpiled and untranspiled code in npm packages?
The status quo
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”):
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 (
es2015) or beyond what’s currently supported in Node.js (
pkg.foo is an abbreviation for “property
I propose to use
package.json as follows:
esnext: source code using stage 4 features (or older), not transpiled, in ES modules.
main continues to be used the same way. Its code can be generated from the untranspiled code.
module use cases should be handleable via
browser can be handled via an extended version of
esnext (see next section).
The next blog post in this series describes how to implement this approach.
This is an excerpt of a
package.json using the new property:
Extended versions of
An extended version of
pkg.esnext could be a JSON object with two properties:
main: points to the untranspiled code.
browser: points to browser-specific untranspiled code.
This would look as follows:
webpack can be configured to recognize alternatives to
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
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.