Mastering legacy browser support: core-js, browserslist, babel

Honza Hrubý
MewsDevs
Published in
4 min readAug 23, 2023

As developers, we love to use the latest and greatest technology available. At the same time, we need to make sure everything will work smoothly for our users. In the frontend world, there’s a huge difference between the code we write and what eventually gets to our users. Let’s have a look at the tools that enable us to write modern code.

The tools

We will need two main ingredients: transpilers and polyfills.

Transpilers take source code written in some programming language, transform it, and return the output in the same language. The result is still human-readable, as opposed to compilers, which typically return machine-readable byte code.

Polyfills implement missing pieces of functionality in the browser itself. When there’s a new feature or standard, the browsers need time to implement it. Even when they do, we cannot rely on our users having installed the latest version of the browser.

Now let’s dive into these tools in more detail. Here’s an example of a commonly used setup:

browserslist

When you decide what browsers you want to support in your app, you can formalize it using a browserslist query. Choose a specific version, date of release, usage share, etc., and browserslist then converts the query into a format other tools can understand.

Typically, you will have a different configuration for local development and production build. Developers tend to have the latest browser versions, so there’s no need for additional overhead.

You can play with browserslist at https://browserslist.dev.

Example of browserslist configuration

core-js

A collection of polyfills and likely the most widely used one. It’s highly modular, meaning you can pick only the pieces you need for your code to work, manually or automatically.

Core-js stays on the cutting edge of javascript, which allowed us to use ES6 array methods (e.g., find, includes, some) way before they became natively supported in browsers. Another example is Object.entries or, more recently, Array.at.

The complete list of available features can be found at https://github.com/zloirock/core-js#features.

babel

Now you might be wondering how all this connects and how we can make use of it. The connecting link here is babel. A transpiler that transforms the source code according to our settings, optimizes it and injects the polyfills needed.

Babel can do more than that and is highly configurable through presets and plugins. The scenario described above is achieved through the most popular preset called @babel/preset-env.

Initially, babel was called 6to5 because all it did was transform the new ES6 syntax and functions to ES5, which browsers at the time could understand.

It is fair to point out that babel is not as fast as its modern competitors, such as SWC, but going down this rabbit hole of tooling would make for a separate series of articles.

Where’s the catch?

We need to be aware of some trade-offs caused by these tools, such as:

  • degraded performance
  • increased bundle size
  • longer build time

If we go too far on the safe side (e.g., support IE11), we will make a worse user experience for everyone else. It is therefore important to strike a balance. For that, browserslist has some reasonable defaults based on the global market share of each browser. And for individual features, there are two great sites that also act as compatibility databases. They map each feature to a specific version of the browser in which the feature has been implemented: Can I use and MDN.

It’s also important to know that core-js doesn’t include all the polyfills you might need. There are features such as Fetch API or ResizeObserver that you have to provide manually.

Example time

Let’s put all this into practice. There’s a great online playground on https://babeljs.io/repl where you can try all the different babel configurations, even with an integrated browserslist field.

Here’s an example showing how Array.at and optional chaining are treated, using Firefox as the target browser.

The base image is targeting Firefox 73 where none of these were supported
Optional chaining was implemented in Firefox 74, see how it’s no longer transformed
Array.at was added in Firefox 90, polyfill is no longer included

How do I use this?

It may sound surprising, but I wouldn’t recommend setting this up from scratch. There’s much more to the whole setup than what we’ve covered here. But hey, good news! In most frameworks and meta-frameworks, such as create-react-app or Next.js, all of this is already set up for you. You only need to decide what browsers you want to support, configure browserslist and add import 'core-js/stable' to your main entry file.

Summary

In this post, we’ve explored how browserslist, core-js, and babel team up to make web development a success. Just like a chef blending flavors, these tools work together to ensure your modern code runs smoothly on all browsers. The secret ingredient is finding just the right balance of compatibility and performance.

Further reading

Originally published at https://developers.mews.com on August 23, 2023.

--

--