Tuning Babel to your runtimes

A talk by Christophe Porteneuve at dotJS 2016

whoami


const christophe = {
  age: 39.0848733744011,
  family: { wife: 'Élodie', son: 'Maxence' },
  city: 'Paris, FR',
  company: 'Delicious Insights',
  trainings: ['JS Total', 'Node.js', 'Git Total'],
  upcoming: ['ES2015+', 'GitHub', 'Performance Web'],
  webSince: 1995,
  claimsToFame: [
    'Prototype.js',
    'Ruby On Rails',
    'Prototype and Script.aculo.us',
    'Paris Web',
    'NodeSchool Paris'
  ]
}
          

ES2015/ES6+

in 2016

What’s the native support like?

It’s pretty sweet, actually. Take ES0215:

  • Edge 14 = 93%
  • Firefox 53 = 96%
  • Chrome 53+ / Opera 40+ = 97%
  • Safari 10 (incl. iOS 10) = 100%
  • Node 6.8+ (LTS) = 99%

And 90% is usually plenty enough 😉

OK but… In my real life

Native ES2015 modules are still a work in progress.
(Just wait for a later talk today…)

But we want that syntax now! I mean, it’s so awesome:


import React, { PropTypes } from 'react'

export default function Gauge ({ value, max = 100 }) {
  return <LinearProgress mode='determinate' value={value} max={max} />
}
            

And what about older browsers? What about IE? What about older Firefox, Chrome, or just Node 4.x?

And what about ES2016+, btw?

We just talked about ES2015, but we’re at ES2016 now, right?

And next year, what do we do with ES2017? And the year after that?

Babel !

rulez

Maybe not all the things, tho?

Starting with Babel 6, we can cherry-pick the syntaxes we want to transpile. Everything else is left as-is (deemed native).

The bigger the native support gets, the smaller our transpiled share needs be. There is a strong incentive to avoid the “bulldozer,”, transpile-everything approach we get in most tutorials, generators and starter kits.

We build / watch faster, and eventually run faster (as native catches with, then overcomes, transpiled code performance).

Plugins

Syntaxes and transforms

A syntax plugin lets the parser understand a syntax amidst your JS code stream. But Babel doesn’t necessarily transpile it.

A transform plugin instructs Babel to transpile stuff towards ES5. It automatically leverages any syntax plugin it may need.

But there are tons: Babel currently offers 60+ transforms, 21 of which are just about ES2015+!

Presets

So we get presets: theme-based groupings of plugins for a specific purpose. These fan out like so…

  • 6 official (es2015, es2016, es2017, latest, env and react),
  • 4 experimental, focused on TC-39 stages (stage-0 thru stage-3),
  • and 750+ community-based!

Of course you can mix and match presets and plugins.

Much more than ES2016

As we’ve seen, Babel already handles ES2016, ES2017, and stages that will feed into later versions. For instance:

And we also have (official!) plugins for third-party syntaxes (most notably JSX and Flow annotations), module conversion (e.g. ES2015 ➡️ CommonJS), minification/optimization (notably for React)…

So how do I tell Babel?

Most people use a JSON .babelrc file

If you run all your Babel stuff through npm scripts though, you may elect to stuff your Babel config in your package.json (in a "babel" key).


{
  "presets": ["latest-minimal"],
  "plugins": [
    "transform-object-rest-spread", "transform-class-properties",
    "transform-function-bind"
  ]
}
            

{
  "presets": [["env", {
    "targets": { "browsers": "> 1%, last 2 versions" }
  }]],
  "plugins": [
    "transform-object-rest-spread", "transform-class-properties",
    "transform-function-bind"
  ]
}
            

Show me specs!

Browser side (with IE9+)

You need to transpile pretty much everything. IE9 has 100% ES5 (except the strict mode), but 0% ES2015. IE10 has 3%, IE11… 11%. So there.


"presets": ["es2015"]
            

or


"presets": ["latest"]
            

Browser side (evergreens)

Pick the last 2 versions (a la Autoprefixer), trim to 1+% browser share, check out Kangax’s compatibility table, cross-ref everything…

Or just use env:


"presets": [
  ["env", {
    "targets": {
      "safari": 10, // SF9 is only 54%…
      "browsers": "> 1%, last 2 versions"
    },
    "debug": true
  }]
]
            

Node

There are specific presets, maintained by the community. But just go with generic feature detection:


"presets": ["latest-minimal"]
            

or


"presets": [
  ["env", {
    "targets": { "node": "current" },
    "debug": true
  }]
]
            
(that being said, I would upgrade to 6.x for LTS if I were you. Just sayin’.)

What about TypeScript?

TypeScript vs. ES2015+

TS ≈ ES2016 + optional type annotations (+ JSX compat)

TS2 transpiles to ES5 by default, but can target ES2015.

Built in VS2013u2 and later + VSCode.

Not much type inference; mostly relies on annotations.

As often with IntelliSense, many external pre-compiled typedefs to avoid deep inference. Enormous repository on DefinitelyTyped.

Bringing TypeScript in

The documentation lists a number of “classic” integrations: Browserify, Webpack, Grunt, Gulp, JSPM/Rollup, etc.

TL;DR: works pretty well.

A word on Flow

TS isn’t the only way to get fine-grained type information on JS code. The Flow project, from Facebook, is excellent.

I often heard that Flow feedback messages were more useful/actionable than TS’, but haven’t really compared them personally.

Do note that Babel can go through Flow-annotated code.

Thanks!

And may JS be with you.


Christophe Porteneuve

@porteneuve

Slides are at bit.ly/dotjs-babel-tuning