Leveling Up Your Codebase With Babel

Modern Javascript is fun to write but there are also benefits to it. Nate shares what, exactly, babel can do and how it helps us write clean, future-ready code.

Babel compiles the latest JavaScript features to ES5. The latest and greatest JavaScript goes in; backwards-compatible JavaScript comes out. Easy to use? Yes. Ready to drop into your existing codebase? Eh…Let’s talk about it.

It is possible to level up your codebase and not leave your users behind. The purpose of this article is to help ease that evolution and hopefully bring some clarity to what exactly Babel does for us. Hopefully, we can save some time for those of us who are just boarding the train to the land of modern JavaScript.

I Want the New Stuff

Give me all the JS features. I want ‘em all. I gotta have ‘em. Gimme.

Do we all feel this way? Probably not everyone, but I personally fall into the category of developer who enjoys writing obnoxiously futuristic JavaScript. Most of us at Sparkbox fall into this category. Besides being super enjoyable, there are some real benefits to exploring all these new language features. It makes my job more fun, it keeps me on a learning path, and it means that I will be less confused as new iterations of the language roll out. Syntactical changes have made the language a lot more fun to write. New built-in prototypes and methods provide a lot of convenience. Also, investing time to learn the core language itself will surely carry value further than learning how to use the latest framework. All of this is great for me as a developer. However, as much as I selfishly want to enhance my development experience, ultimately I have one core task as a developer: deliver a positive, usable web experience.

User Experience vs Developer Experience

At Sparkbox, we strive to create the most robust web experiences that we can. We utilize progressive enhancement methodologies as our default approach to building accessible and, as much as possible, fault-tolerant web things. Progressive enhancement pushes us to create more resilient products that serve a broad set of devices and browsers. We firmly believe in this approach, but it doesn’t always align with our desire to stay on the cutting edge with our development workflow.

…investing time to learn the core language itself will surely carry value further than learning how to use the latest framework.

The web is being used by an ever-growing variety of browsers. As developers, we want to take advantage of features available in JavaScript, but we have to be mindful of the support available to browsers in the wild. Since 2015, JavaScript has been growing rapidly. Tons of new features have been implemented in evergreen browsers, but many users are still on platforms that require us to ship ES5 code. This is where our good friend Babel comes in.

So What Does Babel Do?

First of all, we need to understand what Babel does and doesn’t do for us. What it does do is transform syntax. It allows you to write code in a JavaScript version (or superset) of your choosing and transform it into a different version of JavaScript. In nerd terms, it’s a source-to-source compiler. What goes in and what comes out is based on the plugins and/or presets that you tell Babel to use. The most common compilation path we currently use at Sparkbox is ES2017 → ES5.

Out of the box, Babel doesn’t provide anything in terms of new JS “features.” Babel is a plugin-based system where you enable or configure the features you want to transform. Configuring your compilation path is fairly straightforward. A file named .babelrc, written in JSON and placed in the root of a project will do the trick. This is what tells Babel how to transform the input source code. Babel accepts many options, but the two that we use to configure our compilation are presets and plugins. Presets are just collections of plugins and/or other presets. So ultimately, every transformation you specify with these two options, breaks down to a list of plugins.

Here’s the full list of the plugins that make up the babel-preset-latest preset:

babel-preset-latest

  • babel-preset-es2015

    • check-es2015-constants

    • transform-es2015-arrow-functions

    • transform-es2015-block-scoped-functions

    • transform-es2015-block-scoping

    • transform-es2015-classes

    • transform-es2015-computed-properties

    • transform-es2015-destructuring

    • transform-es2015-duplicate-keys

    • transform-es2015-for-of

    • transform-es2015-function-name

    • transform-es2015-literals

    • transform-es2015-modules-commonjs

    • transform-es2015-object-super

    • transform-es2015-parameters

    • transform-es2015-shorthand-properties

    • transform-es2015-spread

    • transform-es2015-sticky-regex

    • transform-es2015-template-literals

    • transform-es2015-typeof-symbol

    • transform-es2015-unicode-regex

    • transform-regenerator

  • babel-preset-es2016

    • transform-exponentiation-operator
  • babel-preset-es2017

    • syntax-trailing-function-commas

    • transform-async-to-generator

…and the .babelrc file to get all those transformations looks like this:

{
  "presets": [“latest”]
}

What is a Feature?

Good question. Here’s my best attempt at a definition:

Features of a language are defined by its written specification. The implementation of those features is up to the environment in which the language runs. Things like global objects built into JavaScript and the methods available on those objects constitute features. Things like Array, Map, and Promise are features. Also, control structures like for loops and if statements help make up the set of features.

Common Pitfalls

Using Babel to output code that uses x feature does not ensure that x feature will be available at runtime. To understand what can be used, you need to understand what all these transformations do and what your target runtime is capable of. The Babel repl is a great way to examine output, but I’d like to outline a few common hangups we’ve encountered, so that hopefully you can avoid them.

IE8, Yep People Still Use It

If IE8 is still in the grouping of browsers you support, there are a couple plugins that Babel will need to get the job done properly. I know IE8 is less and less common these days, but we actually just finished a project that required support of IE7, if you can believe it. There are still markets that need this and people in industries tied to old machines with no other choice. IE8 doesn’t like the use of reserved words as property names. Using bracket notation will handle the conflict of something like the following example.

var myObj = {
  var: function() { console.log(this); }
};

myObj.var(); // err in IE8

Add these two plugins to your Babel config–transform-es3-member-expression-literals and transform-es3-property-literals. Those will turn the previous code into this…

var myObj = {
  "var": function () {
    console.log(this);
  }
};

myObj["var"](); // Works in IE8

The Spread Operator

There’s a pattern for making DOM selections that we’ve come to use a lot at Sparkbox. It looks like this…

const select = (selector, root = document) => {
  return [...root.querySelectorAll(selector)];
};

Note the use of the spread operator, which ensures we return a proper array as opposed to a NodeList. This means we can safely call methods like map and forEach on the returned array. Not too shabby.

But hold on one minute there! Our Babel config, preset-latest, will use transform-es2015-spread, which will ultimately compile to Array.from. Browser support for this is generally not good enough for the projects we work on. At least not yet. Our solution (for now) is to use a polyfill for Array.from.

Promises +

Promises in JavaScript are a pretty common thing these days. They make handling asynchronous code a lot friendlier. I ❤️ promises. The native fetch API relies on them. Using fetch to make network requests is a better version of jQuery.ajax. It has a powerful yet easy-to-use API. Using fetch along with our DOM selection method from above can replace a huge portion of our jQuery usage. Bye-bye large code dependencies!

Promises were a part of the ES2015 spec, but they aren’t something that you get for free by using preset-latest. Go ahead and check the listing from above. You won’t find anything about promises. This is the important thing to understand when using Babel. It gives us a ton of features, but it doesn’t magically fix all our problems.

This isn’t limited to promises. To get a better idea of what requires a polyfill, just check the polyfill section on the Babel homepage. I would also recommend checking out the caveats page that has been so lovingly provided to us.

Polyfills

If the term polyfill is new to you, here’s the definition. Yes, polyfills are technically a regressive enhancement, which is not perfectly in line with the progressive approach we strive to uphold. In most cases, however, polyfills are a great choice for writing future compliant code that is both easier to maintain and doesn’t add long-term dependencies to our codebase. One nice thing about using polyfills is that they only need to exist in your application for as long as your target browser-set dictates. At some point in the future, you will be able to burn them, and who doesn’t love deleting useless code? That means code reduction for free. Yes, you will probably still end up adding more polyfills for newer features as time goes on, but hey, you’ll be writing native JS, which comes with its own set of advantages.

Babel offers some good ways to automate the polyfilling. The transform-runtime plugin is a great option if you’re building an application targeted for Node. It’s not necessarily suited for browsers. A cool, newer option for browsers is to use preset-env with the option useBuiltIns to pull in the needed parts of babel-polyfill. The env preset can target a specific set of browsers and use only the transformations necessary for the given set. It’s a great choice in general. With the useBuiltIns option, it would be nice to only get the polyfills that are actually used in your codebase. That way, we wouldn’t need to ship unnecessary code to our users. At the moment, there’s an issue covering this very problem but has yet to be resolved.

While Babel does provide several ways to automate polyfilling, we prefer a more progressive and tailored approach to shipping this type of “helper” code. Core-js provides all the JavaScript polyfills we need. For the time being, we can hand-select modules from core-js and import them on an as-needed basis. This takes very little work and means we don’t send extra cruft down the wire. An even better approach would be to break the polyfills off into their own file and use feature detection at the entry point of our application to load the file on demand. Keep in mind that you need to run polyfill code before anything else is evaluated. This gist is a good example of that technique. It prepends the polyfill script to an array of resources to load and ensures that they load synchronously.

It’s probably worth noting that at some point in the future, we will begin to ship bare ES2015 code as our target environments begin to support more. Already today, some companies are serving pure ES2015 to browsers that support it.

Modules

While this article is focused on Babel and how it compiles JavaScript, it does not address ES6 modules and the complications surrounding them. At the time of this writing, Webpack is far and away the most popular tool being used as a solution to handle modules written for the browser. While modules are outside the scope of this article, they are an important part of writing an organized modern JS app. If you want to learn more about Babel, module bundlers, and our current JavaScript toolchain, you might consider attending one of our Modern Javascript workshops.

This is the important thing to understand when using Babel. It gives us a ton of features, but it doesn’t magically fix all our problems.

Babel On

Using Babel and progressively loading polyfills can help you maintain a more future-friendly codebase and deliver smaller payloads to users while still providing support for older browsers.