Old & New

Some of the JS features we’ve already covered, and certainly many of the features covered in the rest of this series, are newer additions and will not necessarily be available in older browsers. In fact, some of the newest features in the specification aren’t even implemented in any stable browsers yet.

So, what do you do with the new stuff? Do you just have to wait around for years or decades for all the old browsers to fade into obscurity?

That’s how many people think about the situation, but it’s really not a healthy approach to JS.

There are two main techniques you can use to “bring” the newer JavaScript stuff to the older browsers: polyfilling and transpiling.

Polyfilling

The word “polyfill” is an invented term (by Remy Sharp) (https://remysharp.com/2010/10/08/what-is-a-polyfill) used to refer to taking the definition of a newer feature and producing a piece of code that’s equivalent to the behavior, but is able to run in older JS environments.

For example, ES6 defines a utility called Number.isNaN(..) to provide an accurate non-buggy check for NaN values, deprecating the original isNaN(..) utility. But it’s easy to polyfill that utility so that you can start using it in your code regardless of whether the end user is in an ES6 browser or not.

Consider:

  1. if (!Number.isNaN) {
  2. Number.isNaN = function isNaN(x) {
  3. return x !== x;
  4. };
  5. }

The if statement guards against applying the polyfill definition in ES6 browsers where it will already exist. If it’s not already present, we define Number.isNaN(..).

Note: The check we do here takes advantage of a quirk with NaN values, which is that they’re the only value in the whole language that is not equal to itself. So the NaN value is the only one that would make x !== x be true.

Not all new features are fully polyfillable. Sometimes most of the behavior can be polyfilled, but there are still small deviations. You should be really, really careful in implementing a polyfill yourself, to make sure you are adhering to the specification as strictly as possible.

Or better yet, use an already vetted set of polyfills that you can trust, such as those provided by ES5-Shim (https://github.com/es-shims/es5-shim) and ES6-Shim (https://github.com/es-shims/es6-shim).

Transpiling

There’s no way to polyfill new syntax that has been added to the language. The new syntax would throw an error in the old JS engine as unrecognized/invalid.

So the better option is to use a tool that converts your newer code into older code equivalents. This process is commonly called “transpiling,” a term for transforming + compiling.

Essentially, your source code is authored in the new syntax form, but what you deploy to the browser is the transpiled code in old syntax form. You typically insert the transpiler into your build process, similar to your code linter or your minifier.

You might wonder why you’d go to the trouble to write new syntax only to have it transpiled away to older code — why not just write the older code directly?

There are several important reasons you should care about transpiling:

  • The new syntax added to the language is designed to make your code more readable and maintainable. The older equivalents are often much more convoluted. You should prefer writing newer and cleaner syntax, not only for yourself but for all other members of the development team.
  • If you transpile only for older browsers, but serve the new syntax to the newest browsers, you get to take advantage of browser performance optimizations with the new syntax. This also lets browser makers have more real-world code to test their implementations and optimizations on.
  • Using the new syntax earlier allows it to be tested more robustly in the real world, which provides earlier feedback to the JavaScript committee (TC39). If issues are found early enough, they can be changed/fixed before those language design mistakes become permanent.

Here’s a quick example of transpiling. ES6 adds a feature called “default parameter values.” It looks like this:

  1. function foo(a = 2) {
  2. console.log( a );
  3. }
  4. foo(); // 2
  5. foo( 42 ); // 42

Simple, right? Helpful, too! But it’s new syntax that’s invalid in pre-ES6 engines. So what will a transpiler do with that code to make it run in older environments?

  1. function foo() {
  2. var a = arguments[0] !== (void 0) ? arguments[0] : 2;
  3. console.log( a );
  4. }

As you can see, it checks to see if the arguments[0] value is void 0 (aka undefined), and if so provides the 2 default value; otherwise, it assigns whatever was passed.

In addition to being able to now use the nicer syntax even in older browsers, looking at the transpiled code actually explains the intended behavior more clearly.

You may not have realized just from looking at the ES6 version that undefined is the only value that can’t get explicitly passed in for a default-value parameter, but the transpiled code makes that much more clear.

The last important detail to emphasize about transpilers is that they should now be thought of as a standard part of the JS development ecosystem and process. JS is going to continue to evolve, much more quickly than before, so every few months new syntax and new features will be added.

If you use a transpiler by default, you’ll always be able to make that switch to newer syntax whenever you find it useful, rather than always waiting for years for today’s browsers to phase out.

There are quite a few great transpilers for you to choose from. Here are some good options at the time of this writing: