The State of ES5 on the Web

In 2017, I wrote an article showing web developers how they could deploy ES6+ code (a.k.a. ES2015) to production, without needing to transpile it to ES5. This technique was liberating for website developers who wanted the freedom to write modern code without having to worry about transpiler or polyfill bloat.

Unfortunately, while many website developers were able to use this technique, most JavaScript library authors could not.

Library authors face a lot more constraints than website developers, because they don’t control how their code is deployed. Also, since many of the popular build tools recommend developers exclude their node_modules directory from transpilation, library authors were forced to be extremely conservative—usually transpiling all the way back to ES5 in order to avoid potentially breaking sites.

But that was seven years ago, and there’s been a ton of advancement in the JavaScript tooling space since then. The browser landscape has also changed tremendously. Most notably IE 11, the last remaining ES5 browser, stopped being supported by Microsoft in 2022, which meant many businesses could finally stop supporting it as well.

So what is the current state of ES5 on the web today? And what are the best practices for web developers when building code for production?

This article looks at the data we have to answer these questions. It also offers some concrete recommendations (based on that data) for how both website developers and library authors should approach legacy browser support moving forward.

Quick disclaimer

Before digging into the real-world data on ES5 usage, I want to clarify that there is nothing inherently wrong with either authoring or publishing ES5 code.

JavaScript engines have been optimizing for ES5 code for much longer than they have for modern code, so if you have old ES5 code that is still working, there’s no reason to update it just to make it “modern”.

However, if you’re authoring code in ES6+ syntax and then using a build tool to transpile it to ES5, that generally results in a lot of polyfill and transpiler bloat that can significantly increase the size of your final bundles.

To illustrate this point, here’s an example:

console.log([1, 2, 3].at(-1));

If you transpile this code to ES5 by hand, it would probably look something like this:

var arr = [1, 2, 3];
console.log(arr[arr.length - 1]);

However, if you transpile this single line of code with Babel and configure it to add polyfills—even if you limited it to just the needed polyfills based on usage in the source code—it includes 71 core-js dependencies and goes from being 31 bytes to 11,217 bytes minified!

The point of this example is not to shame Babel or core-js. Those tools need to be able to support all possible ES6+ code, which requires them to account for all sorts of edge cases (though this particular example doesn’t have any).

Instead, the point is to emphasize that choosing to support legacy browsers does come with a cost, and that cost can be significant.

Unfortunately, the problem is actually worse than just code bloat. If you look at the data below on how popular websites today are actually transpiling and deploying their code to production, it turns out that most sites on the internet ship code that is transpiled to ES5, yet still doesn’t work in IE 11—meaning the transpiler and polyfill bloat is being downloaded by 100% of their users, but benefiting none of them.

The data

To understand the state of ES5 on the web, you have to look at three things, since all of these play a critical role in the final output of code that we, as web users, receive:

  • The default configurations of popular bundlers and build tools
  • The state of code found in popular JavaScript libraries
  • The state of code deployed by website owners

Default bundler and build tool configurations

Most bundlers and build tools are extremely configurable and offer almost unlimited control over the final output of code. However, in practice, most developers just use the defaults, so the defaults matter a great deal.

What are those defaults? Specifically, do the defaults result in code being transpiled to ES5?

To answer that question I took a look at the output generated by some of the most popular build tools, according to the most recent State of JS survey (2023), ordered roughly by popularity:

ToolDefaults to ES5?Notes
BrowserslistNoNot a build tool itself, but used by many build tools internally and is the most popular open source tool for configuring browser support targets. The defaults setting no longer includes any ES5 browsers. The last one was IE 11, which was marked as dead in version 4.21.
BabelYesBabel's documentation recommends setting a targets option (which uses Browserslist), but if none is specified it will transpile all code to ES5.
webpackNoBy default, webpack does not transpile any code. Most webpack users include babel-loader, and webpack's usage example for that suggests setting targets: "defaults".
TypeScript (tsc)YesTypeScript's default target option is ES5.
Next.jsNoNext.js uses Babel to transpile and by default sets a Browserslist config that targets "modern browsers" (i.e. browsers that support ES modules).
esbuildNoesbuild does not transpile by default. You can set a custom target to enable transpiling, but ES5 is not supported as a transpile target.
ViteNoVite uses esbuild and by default sets custom targets for "modern browsers" (i.e. browsers that support ES modules). Vite allows users to install a plugin if they need to support legacy browsers.
RollupNoRollup does not transpile by default. Many Rollup users install @rollup/plugin-babel, in which case the Babel defaults are used.
ParcelNoParcel automatically applies differential serving with customizable targets.
Closure CompilerNoDefaults to ECMASCRIPT_NEXT, which is the latest set of stable ES features.

As this table shows, the vast majority of bundlers and build tools no longer transpile to ES5 by default. It’s also notable that newer tools do not support ES5 at all, which shows that the trend is moving in that direction.

That said, Babel is still the most popular tool to transpile JavaScript, and as a result transpiling to ES5 is still quite common on the web (see ES5 usage in the wild below for more details).

In addition to looking at the popular build tools, I also looked at some of the most popular libraries in use today (again based on the State of JS survey, in rough popularity order):

To test each of these libraries, I created a bundle entry point that only imported that specific library, using one of the code examples from the library’s documentation. I then bundled the code using both Rollup and Webpack to test the output and see if it included any ES6+ syntax (specifically, any ES6+ syntax that isn’t supported in IE 11).

Here’s what I found:

LibraryContains ES6+ syntax?Notes
LodashNoES5 only
ReactNoES5 only
date-fnsYesarrow functions
three.jsYesasync/await, arrow functions, spread, destructuring
d3Yesarrow functions, spread, destructuring
Framer-motionYesarrow functions, spread, destructuring
greensockNoES5 only
dayjsNoES5 only
ZodYesasync/await, arrow functions, spread, destructuring
RxJSYesarrow functions
immerYesarrow functions, spread, destructuring
luxonYesasync/await, arrow functions, spread, destructuring
react-queryNoES5 only (bundles Babel helpers)

As the results above show, many popular JavaScript libraries are now publishing ES6+ syntax.

This is notable because, as I mentioned earlier, most developers that use Babel to transpile their source files while bundling, explicitly configure their bundler to not transpile anything in the node_modules directory—which was the main reason library authors historically felt they needed to continue transpiling to ES5.

For example, as of when this article was published (September 2024):

And TypeScript (tsc), the second-most popular transpilation tool after Babel, will only transpile a project’s own code files. It will not transpile project dependencies in node_modules.

This creates a problem for any website that wants to support ES5 and is using Babel or tsc to transpile their code. Unless they have a sophisticated understanding of how all the pieces of their build pipeline interact with each other, and unless they know how to properly configure each of them, they’re likely bundling ES6+ code in with their ES5 code without realizing it.

So this raises the question, is this actually causing a problem for real websites, or are most of them properly configuring their tools? The next section looks at data from HTTP Archive to answer that question.

Note: some of the libraries in the table above publish both ES5 and ES6+ versions, typically with the ES5 version set on the package.main field, and the ES6+ version set on either the package.module or package.exports fields. In these cases I only looked at whatever version of the script was getting pulled in by the bundler when using the default configuration (since that’s what most people use) and bundlers today default to using package.module or package.exports over package.main (see: [1], [2], [3]).

ES5 usage in the wild

The three main tools developers use to transpile ES6+ code to ES5 are:

  • Babel
  • TypeScript (tsc)
  • Closure Compiler (a.k.a. JSCompiler internally at Google)

All three of these tools include some form of polyfills and what are known as ES5 “helper” functions to avoid duplication in the final output. The most common ES5 helper function libraries used by these tools are: babel-helpers, core-js, regenerator-runtime, tslib, and $jscomp.

Many of the functions in these helper libraries are unique enough that it’s possible to detect (even in minified code) which sites are using them by querying the HTTP Archive. Searching for the presence of these helper functions—rather than standard ES5 syntax (such as var or non-arrow function)—helps to differentiate old ES5 code written by hand (usually fairly optimized) from newer ES5 code generated by a transpiler (usually fairly bloated).

I did a search on HTTP Archive to see how common it was for popular websites (top 10,000, based on CrUX popularity ranking) to include these helpers in the script bundles they deploy to production. I also wanted to see how common it was for sites to serve untranspiled ES6+ syntax.

Here’s what I found (full results):

  • 89% of sites serve at least 1 JavaScript file containing untranspiled ES6+ syntax.
  • 79% of sites serve at least 1 JavaScript file containing ES5 helper code.
  • 68% of sites serve at least 1 JavaScript file containing both ES5 helper code as well as untranspiled ES6+ syntax in the same file.

This last finding kinda blew my mind.

To reiterate what I said earlier—because it warrants repeating—if a browser does not support ES6+ syntax (such as IE 11), then it will error when trying to load a script file that contains ES6+ syntax. And if the browser does support ES6+ syntax, then it doesn’t need any of that ES5 helper code or any of the legacy polyfills. There is absolutely no reason to include both.

To double check that the results of this query were accurate, I manually tested 20 random sites on the list and confirmed that they do in fact include both the ES5 helper code as well as ES6+ syntax in some of the same script bundles. I also manually visited those sites in IE 11 and confirmed that those script bundles do indeed fail to load.

Keep in mind that these are not just random sites on the Internet. These are the 10,000 most popular websites in the world, which account for the vast majority of all web usage globally.

What does all this mean?

For a site to serve users code that contains both ES5 helpers and untranspiled ES6+ syntax, there’s really only two plausible explanations:

  1. The site doesn’t need to support ES5 browsers, but some of their dependencies transpile to ES5, so therefore ES5 code appears in their output.
  2. The site intended to support ES5 browsers, but they didn’t realize that some of their dependencies publish untranspiled ES6+ syntax, and they didn’t configure their bundler to transpile code in node_modules.

Regardless of the explanation, the fact that so many of the world’s most popular websites are serving so much unnecessary code, is a strong indicator that the defaults our tools currently recommend are not working.

If there’s any silver lining in this data, it’s that it’s pretty clear to me that dropping IE support is not going to have a noticeable impact on most businesses. If all of these major companies are apparently not impacted by these broken IE experiences, yours probably won’t be either.

Recommendations

For library authors

The original rationale for why library authors should transpile to ES5 was that most sites needed to transpile to ES5 anyway. However, given that 89% of the top 10,000 websites currently ship some untranspiled ES6+ syntax, that rationale is no longer valid.

Given the data presented in this article, it definitely does not make sense for JavaScript library authors to be transpiling their code to ES5 anymore.

Practically speaking, library authors have no information about the browser support needs of the websites importing them, so it doesn’t make sense for them to be making that decision for all consumers of their library. At the same time, library authors shouldn’t assume that all consumers of their library will be able to run it through a sophisticated build process, so it’s important that their published code uses fully standard JavaScript and works in the current set of widely used browsers.

So what targets should library authors choose? In my opinion the best solution for library authors is to use Baseline—specifically to only include Baseline Widely Available features in any published code.

If you’re not familiar with Baseline, it’s an effort by the WebDX Community Group within the W3C to help developers easily identify features that are stable and well supported by all major browsers and browser rendering engines across desktop and mobile. A feature is considered Baseline Widely Available if it has been available in stable versions of all four major browsers for at least 30 months.

The main benefit of targeting something like Baseline Widely Available is that it’s a moving target, meaning it won’t get stuck in the past, like what happened with targeting ES5 (and what is currently happening with the esmodule target used by Next.js, Vite, and Parcel).

Library authors can configure their build system to target Baseline Widely Available features now with the following Browserslist query (for any tool that supports Browserslist):

targets: [
  'chrome >0 and last 2.5 years',
  'edge >0 and last 2.5 years',
  'safari >0 and last 2.5 years',
  'firefox >0 and last 2.5 years',
  'and_chr >0 and last 2.5 years',
  'and_ff >0 and last 2.5 years',
  'ios >0 and last 2.5 years',
]

Note: there’s an open feature request to add Baseline support to Browserslist, which would simplify the above query to just “baseline widely available”.

If a site needs to support more browsers than those covered by Baseline Widely Available, that’s 100% fine. That site can always configure their build system to further transpile any libraries they’re importing. The point is this is a decision best made by the website developers, not the library author.

For website developers

The fact that so many popular websites ship both untranspiled ES6+ syntax and ES5 helpers in the same script bundle is a clear indication that the practice of excluding the node_modules directory from transpilation is not a good practice.

I’ve been arguing that this is not a good practice since 2017, but most developers I talked to didn’t want to follow this advice because doing so would slow down their builds.

These days, though, build tools have gotten significantly faster. Also, sites can configure their builds to only process code in node_modules when building for production. In development the code should run just fine on any browser that the developer is using, especially if library authors take the advice I gave above and target Baseline Widely Available.

Final thoughts

As I mentioned above, the point of this article is not to blame or shame websites or tool authors based on these findings. I also want to make sure it’s clear that I’m definitely not suggesting that websites should still be supporting IE 11. If anything, these findings suggest that supporting IE 11 is not a necessity for most businesses, even large ones with a global customer base.

The main points I want readers to take from this article are:

  • ES5 is no longer something that build tools or JavaScript libraries should target by default.

    If tools still want to offer ES5 support, it should be something individual sites with specific support needs can opt into.

  • Build tools and libraries should not use a fixed browser support policy.

    These policies can quickly become outdated, which leads to the scenario highlighted by the data in this article. Browser support decisions should be made by the site itself, not the tools it's using. A good browser support policy for tools and libraries is Baseline Widely Available.

  • Website developers who import third-party libraries should process those libraries as part of their build.

    It's not safe to assume that all library authors have the same browser support needs as you. And as the data in this article shows, in many cases website developers may have wider browser support needs than the libraries they're importing (and thus need to further transpile them).

  • Cross-browser support is not something that you should solely rely on your build tool to handle for you.

    If you need to support a specific set of browsers, then you need to be testing your site to ensure it works in those browsers.

Hopefully this article has been useful to anyone who still worries about ES5 support, or to anyone trying to convince others not to worry!

If you’re curious whether your site is serving script bundles that contain legacy ES5 helpers mixed in with untranspiled ES6+ code, you can search for your site in the data I shared above to see for yourself.

And if you discover that your site is doing this and you’re able to fix it based on recommendations from this article, I’d love to hear from you!