Transpiling
Made even worse by the rapid evolution of features, a problem arises for JS developers who at once may both strongly desire to use new features while at the same time being slapped with the reality that their sites/apps may need to support older browsers without such support.
The way ES5 appears to have played out in the broader industry, the typical mindset was that code bases waited to adopt ES5 until most if not all pre-ES5 environments had fallen out of their support spectrum. As a result, many are just recently (at the time of this writing) starting to adopt things like strict
mode, which landed in ES5 over five years ago.
It’s widely considered to be a harmful approach for the future of the JS ecosystem to wait around and trail the specification by so many years. All those responsible for evolving the language desire for developers to begin basing their code on the new features and patterns as soon as they stabilize in specification form and browsers have a chance to implement them.
So how do we resolve this seeming contradiction? The answer is tooling, specifically a technique called transpiling (transformation + compiling). Roughly, the idea is to use a special tool to transform your ES6 code into equivalent (or close!) matches that work in ES5 environments.
For example, consider shorthand property definitions (see “Object Literal Extensions” in Chapter 2). Here’s the ES6 form:
var foo = [1,2,3];
var obj = {
foo // means `foo: foo`
};
obj.foo; // [1,2,3]
But (roughly) here’s how that transpiles:
var foo = [1,2,3];
var obj = {
foo: foo
};
obj.foo; // [1,2,3]
This is a minor but pleasant transformation that lets us shorten the foo: foo
in an object literal declaration to just foo
, if the names are the same.
Transpilers perform these transformations for you, usually in a build workflow step similar to how you perform linting, minification, and other similar operations.
Shims/Polyfills
Not all new ES6 features need a transpiler. Polyfills (aka shims) are a pattern for defining equivalent behavior from a newer environment into an older environment, when possible. Syntax cannot be polyfilled, but APIs often can be.
For example, Object.is(..)
is a new utility for checking strict equality of two values but without the nuanced exceptions that ===
has for NaN
and -0
values. The polyfill for Object.is(..)
is pretty easy:
if (!Object.is) {
Object.is = function(v1, v2) {
// test for `-0`
if (v1 === 0 && v2 === 0) {
return 1 / v1 === 1 / v2;
}
// test for `NaN`
if (v1 !== v1) {
return v2 !== v2;
}
// everything else
return v1 === v2;
};
}
Tip: Pay attention to the outer if
statement guard wrapped around the polyfill. This is an important detail, which means the snippet only defines its fallback behavior for older environments where the API in question isn’t already defined; it would be very rare that you’d want to overwrite an existing API.
There’s a great collection of ES6 shims called “ES6 Shim” (https://github.com/paulmillr/es6-shim/) that you should definitely adopt as a standard part of any new JS project!
It is assumed that JS will continue to evolve constantly, with browsers rolling out support for features continually rather than in large chunks. So the best strategy for keeping updated as it evolves is to just introduce polyfill shims into your code base, and a transpiler step into your build workflow, right now and get used to that new reality.
If you decide to keep the status quo and just wait around for all browsers without a feature supported to go away before you start using the feature, you’re always going to be way behind. You’ll sadly be missing out on all the innovations designed to make writing JavaScript more effective, efficient, and robust.