David Else TypeScript/JavaScript software developer | elsewebdevelopment.com

Why you don’t need Babel

7 min read 2175

Why You Don't Need Babel

In 2020, frontend developers are still wasting a lot of time with excessive tooling. Babel is seen by some as a necessity, but I aim to show you that it’s not.

By the end of this article, you will know:

  • How to confirm which browsers actually need supporting on a case-by-case basis
  • How to lint with Visual Studio Code to avoid the need for Babel
  • Another software alternative to get the same job done faster

What is Babel and what problem does it solve?

Babel is a compiler that converts your modern JavaScript to run in older browsers. It can also perform other jobs such as converting JSX syntax, but it is not the only tool for that.

As browsers evolve, new APIs and ECMAScript features are added. Different browsers evolve at different speeds and prioritize different features. This leaves us with a tricky problem to solve: how can we support them all and still use modern features? Some will be incompatible.

A common solution is to write using the latest features and transpile down to older-style code the browser will understand. Transpiling describes a specialized type of compilation. It has different meanings in different contexts. In our case, there are also two separate parts to transpiling.

The difference between transpiling and polyfilling

Transpiling is the process of converting newer language syntax that old browsers can’t understand into the old syntax they recognize.

Here is an example of transpiling the let statement:

// the new syntax `let` was added in ECMAScript 2015 aka ES6
let x = 11;

// `let` transpiles to the old syntax `var` if your transpiler target was ES5
var x = 11;

Polyfilling is the process of adding the missing methods, properties, or APIs to the old browsers by supplying your own version of the missing native code.

It can be thought of as filling in the gaps. For example, here is a polyfill for isNaN:

// check if the method `isNaN` exists on the standard built-in `Number` object
if (!Number.isNaN) {
  // if not we add our own version of the native method newer browsers provide
  Number.isNaN = function isNaN(x) {
    return x !== x;
  };
}

The best place to get polyfils is via core-js.

We made a custom demo for .
No really. Click here to check it out.

Transpiling and polyfilling are sometimes impossible and can add a lot of code bloat, it’s best to avoid them altogether if possible. That is why the first alternative we’re going to discuss is the best one.

Alternative No. 1: Don’t support ancient browsers

If users would just upgrade their browsers, we could avoid the hassle of transpiling and they could enjoy the enhanced features and performance of the new browser. Unfortunately, things aren’t that simple.

The main culprit is large corporations having to support legacy software. The classic example is Internet Explorer, which has been a blight on web development from the beginning.

That said, things have improved a lot in recent years. Now, most browsers are evergreen, meaning they’re constantly updated. Microsoft now promotes its evergreen Edge browser, which, conveniently, uses the same V8 engine as Chrome, meaning one fewer engine to support.

To determine whether you have to support a particular browser, ask yourself the following questions.

1. Which browsers are your customers currently using?

If you already have a website or app that serves the same customer base, you can get this information from the analytics software. Below are some recent statistics from a UK-based sound engineering website I’m managing. If it were a JavaScript application with the same customers demographic, I would assume they would be using the same browsers.

List of Browsers

If you don’t have analytic software installed, you won’t know which browsers you need to support. You’ll have to make an educated guess. If you have corporate customers, it’s far more likely you’ll need to support IE11 than if you’re marketing to web-literate technology fans.

When you support a browser, you make a commitment. Do you really want to have additional tests on every release, additional build tooling to configure, and extra dependencies to babysit?

There must be a clear financial reason to go to through all this bother. Will losing those customers who can’t access your site or app cost more than it would to support the browser?

2. Which modern browser features do you want to use?

Using modern language features and browser APIs makes writing code easier, faster, and more fun. It also makes your code more maintainable.

If you’re happy writing ES5 and using XMLHttpRequest(), you definitely don’t need Babel, but you might need some kind of therapy.

3. Which modern browser features do your customers’ browsers support?

This data is available on via Can I use, but it’s a waste of time to manually look it up. Now that you know the names of the browsers you want to support, looking up compatible features can be automated with the awesome Browserlist application (more on this in the next section).

Alternative No. 2: Use eslint-plugin-compat

You can avoid the entire transpiling process altogether and instead allow your code editor to alert you if you’re using any features that are too modern for your customers’ browsers. This is the simplest option because it:

  • Eliminates any reliance on transpilers
  • Gives you back hands-on control over your production code

If there’s a modern feature you can’t live without, you can manually polyfill it. Otherwise, you can just use the older syntax when needed.

Create a test

Before we can break down the pros and cons, we need to confirm that our Babel alternatives can do the same basic job. Let’s create a small test.

Below is the modern code we want our target environment to support once transpiled.

After the transportation, there’s a console.assert for each function to verify it’s working as intended. In the case of eslint-plugin-compat we’ll instead check that the incompatible code is being flagged in the linting.

test.js

// test nullish coalescing - return right side when left side null or undefined
const x = null ?? "default string";
console.assert(x === "default string");

const y = 0 ?? 42;
console.assert(y === 0);

// test optional chaining - return undefined on non existent property or method
const adventurer = {
  name: "Alice",
  cat: {
    name: "Dinah",
  },
};

const dogName = adventurer.dog?.name;
console.assert(dogName === undefined);

console.assert(adventurer.someNonExistentMethod?.() === undefined);

// use browser API fetch, to check linting
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then((response) => response.json())
  .then((json) => console.log(json));

Using the eslint env property with eslint-plugin-compat

We need a workaround for linting both language features and browser APIs together.

You can use eslint to check for the language syntax. To do that, change the env property down from es2020.

To check browser API compatibility, use eslint-plugin-compat. It uses the same Browserlist config used by Babel and other tools.

Full instruction can be found in the eslint-plugin-compat repo. We’ll use the browserlist defaults preset to use the default settings. Replace this with your own selection based on your analytics.

What is browserlist?

Browserlist LogoBrowserlist automatically selects a list of browsers based on various criteria you give it.

Have a look at the list of browsers supported by the defaults setting for browserlist. defaults is a shortcut for:

  • > 0.5 percent (browser versions selected by global usage statistics)
  • Last two versions (of every “not dead” browser)
  • Firefox ESR
  • Not dead (browsers without official support or updates for 24 months)

Head to GitHub for the full list of queries available to choose your supported browsers.

Setting up eslint-plugin-compat for Visual Studio Code

Add the following packages to your project.

npm install --save-dev eslint eslint-plugin-compat

Add the following to package.json.

  "browserslist": [
    "defaults"
  ]

Create the following .eslintrc.json file or add these settings to your current one.

{
  "extends": ["plugin:compat/recommended"],
  "env": {
    "browser": true,
    "es2020": true
  }
}

Make sure you have the VS Code ESLint extension installed

Now any browser API that is incompatible with the browserlist config in your package.json is shown as a linting error. You can separately control which version of ECMAScript you want to support using the env property in the .eslintrc.json file.

It would be nice if the eslint-plugin-compat automatically linted the language features as well, but this is currently an open issue.

With the IE 11 setting selected —

Fetch Config Error

— our fetch() API is flagged.

Fetch Error

Change the env property to es6.

Nullish Coalescing Error Config

You’ll immediately see an error trying to use nullish coalescing, which was released as part of Es2020.

Nullish Coalescing Error

Alternative No. 3: Use other software to replace Babel

Before we look at alternatives, let’s quickly review how to use Babel.

Using Babel to transpile and polyfill

First, create a mini-project directory and install the dependencies we need.

mkdir babel-test
cd babel-test
npm init -y
mkdir src dist
npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill

Add the following to your package.json.

"browserslist": "defaults",

Write the test.js file into src, and then issue the following command.

npx babel src --out-dir dist --presets=@babel/env

Finally, run the file to check that the tests still work.

node dist/test.js

There should be no assertion errors, but it will say fetch is not defined since Node.js has no fetch() method. Here is the resulting transpiled code. Observe all the extra cruft and bloat added.

"use strict";

var _ref, _, _adventurer$dog, _adventurer$someNonEx;

// test nullish coalescing - return right side when left side null or undefined
var x = (_ref = null) !== null && _ref !== void 0 ? _ref : "default string";
console.assert(x === "default string");
var y = (_ = 0) !== null && _ !== void 0 ? _ : 42;
console.assert(y === 0); // test optional chaining - return undefined on non existent property or method

var adventurer = {
  name: "Alice",
  cat: {
    name: "Dinah",
  },
};
var dogName =
  (_adventurer$dog = adventurer.dog) === null || _adventurer$dog === void 0
    ? void 0
    : _adventurer$dog.name;
console.assert(dogName === undefined);
console.assert(
  ((_adventurer$someNonEx = adventurer.someNonExistentMethod) === null ||
  _adventurer$someNonEx === void 0
    ? void 0
    : _adventurer$someNonEx.call(adventurer)) === undefined,
); // use browser API fetch, to check linting

fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then(function (response) {
    return response.json();
  })
  .then(function (json) {
    return console.log(json);
  });

The pros and cons of using Babel

Pros:

  • This most basic setup was relatively straightforward
  • Babel has a large community for support and continued updates with 36.8k GitHub stars at the time of writing

Cons:

  • Slow compile time
  • A lot of dependencies, even if they are dev-dependencies (269 packages installed)
  • 39M of disk space used, as reported by du -sh
  • 5728 files installed, as reported by find . -type f | wc -l

Using swc to transpile and polyfill

swc Logoswc is a new competitor to Babel. It is written in Rust and up to 20 times faster. This can be very important if you find yourself waiting a long time to build your project.

To set it up:

mkdir swc-test
cd swc-test
npm init -y
mkdir src dist
npm install --save-dev @swc/cli @swc/core browserslist

Add the following to your package.json.

"browserslist": "defaults",

Write the .swcrc config file into the project root.

{
  "env": {
    "coreJs": 3
  },
  "jsc": {
    "parser": {
      "syntax": "ecmascript"
    }
  }
}

Write your test file into src, then issue the following command to transpile.

npx swc src -d dist

Run the resulting file to check that the tests still work.

node dist/test.js

The resulting swc-transpiled file looks like this:

var ref, ref1;
var ref2;
// test nullish coalescing - return right side when left side null or undefined
var x = (ref2 = null) !== null && ref2 !== void 0 ? ref2 : "default string";
console.assert(x === "default string");
var ref3;
var y = (ref3 = 0) !== null && ref3 !== void 0 ? ref3 : 42;
console.assert(y === 0);
// test optional chaining - return undefined on non existent property or method
var adventurer = {
  name: "Alice",
  cat: {
    name: "Dinah",
  },
};
var dogName =
  (ref = adventurer.dog) === null || ref === void 0 ? void 0 : ref.name;
console.assert(dogName === undefined);
console.assert(
  ((ref1 = adventurer.someNonExistentMethod) === null || ref1 === void 0
    ? void 0
    : ref1.call(ref1)) === undefined,
);
// use browser API fetch, to check linting
fetch("https://jsonplaceholder.typicode.com/todos/1")
  .then(function (response) {
    return response.json();
  })
  .then(function (json) {
    return console.log(json);
  });

The pros and cons of using swc

Pros:

  • swc is much faster
  • Far fewer dependencies (43 packages installed)

Cons:

Other alternatives: Google Closure Compiler and TypeScript

I did not include Google Closure Compiler as an option because it’s notoriously complicated to use. That said, it can do a good job of transpiling and polyfilling. If you have some spare time on your hands, I recommend you check it out — especially if you value small file sizes since its built-in minification is demonstrably superior.

You can also use TypeScript to transpile and core-js to manually polyfill, but this is a clumsy solution that could easily create more problems than it solves.

Conclusion

You don’t automatically need to support old browsers. It’s important to first look at your analytics data to see which browsers your customers are actually using.

If necessary, you can use linting to ensure backward compatibility. This will save you the hassle of creating a special build step and relying on transpilation.

If you do opt for automatic translating, then SWC is much faster than Babel and contains far fewer dependencies. There is also the option to use Google Closure Compiler or TypeScript, but these will require a bit more work to configure.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Full visibility into your web apps

    LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

    In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

    .
    David Else TypeScript/JavaScript software developer | elsewebdevelopment.com

    Leave a Reply