Older browsers don’t support many of the modern features that have been added to JavaScript, such as the array spread operator and the Object.entries
method. To support older browsers while still taking advantage of these modern features, you need a compilation step that transforms the cutting edge JavaScript in your codebase to production code that works in all browsers.
Babel is the most popular tool used to do this transformation. The env preset allows you to transpile JavaScript to be compatible with a list of browsers you wish to support. This article will discuss the effects on compiled JavaScript file size when changing which browsers you support in Babel.
What babel-preset-env does
@babel/preset-env is a smart preset that automatically performs syntax transformations and adds polyfills to your code for a configurable list of target browsers.
Using preset-env, it’s easy to configure Babel to target only the browsers that are relevant to your users. Under the hood, preset-env integrates with Browserslist. You can configure your project to only target browsers that have over 0.5% market share in your specific country or region, or specify a list of specific browsers.
By only supporting browsers that are relevant to your users, you can have a faster, more efficient website, only loading the necessary polyfills and syntax transformations needed for the browsers that are being used by your customers.
Bundle size by list of supported browsers
But how much gain can you expect from this in practice? At DebugBear, we’ve put this to the test, using two different projects: DebugBear’s frontend code, and an open source Kanban project built with React. We used set the useBuiltIns
setting to usage
, so only necessary polyfills are included.
In the bar charts below, the two projects have been compiled with Babel using different browser targets. The “Modern Browsers” setting is targeting all of the browsers listed to the left of it on the chart - Chrome 70 through Edge 16.
For our DebugBear Bundle, there is a 50kB difference in compressed size when targeting Chrome 70+, which was released in December 2018, and IE 11, which was released in 2013. With the major updates to the EcmaScript standard in the past few years, the size difference doesn’t come as much of a shock.
One surprise was that Firefox, iOS, and Safari ended up with a bigger bundle size than Chrome. This was also reflected in the React Kanban project, below.
Under the hood, Babel-preset-env uses core-js to handle polyfilling. Core-js maintains their own list of which polyfills are needed for each browser.
For modern versions of Firefox, core-js polyfills string.prototype.replace to account for a bug with regular expression support. For Firefox 68 and older, core-js polyfills the Promise API entirely, which is about 4KiB gzipped. These added polyfills account for the larger sizes for browsers like Firefox. iOS, Samsung, and Safari do the same.
While the 5 to 13 percent size difference between Chrome and the older browsers probably isn’t noticeable to a user with a fast internet connection, for users with slow WiFi or a spotty mobile connection, it could be a concern.
Increasing the amount of JavaScript also means more time spent parsing and compiling the code. A rough rule of thumb here is that 1MB of JavaScript code takes 1s to parse on a mid-range mobile device.
Is the increase caused mostly by polyfills or syntax transformations?
The chart below shows the difference between our DebugBear bundles with and without polyfills, targeting all browsers with more than .25% market share. Because necessary polyfills are added to the first bundle that they are used in, most of the polyfills are in our app entry bundle. So the size difference for our app bundle is more dramatic than for our non-entry page-specific bundle where few polyfills are added.
This also shows that most of the size penalty for supporting older browsers is because of the polyfills that are added, and not because of syntax transformations.
Dependencies on pre-compiled NPM packages
Many popular NPM libraries are precompiled to ES5 for maximum browser support. If you don’t manually build the dependencies of your project, there is, at least theoretically, some unnecessary code for unsupported browsers.
However, many well-established libraries are still written in ES5 instead of ES6. So most gains from compiling dependencies yourself would not be that fruitful, as the source code is already written in ES5. Additionally, ES6 features that would typically require polyfills aren’t used, or already have polyfills included.
For example, React Core uses the Symbol type, but reverts to using objects if there is no symbol polyfill or native browser support detected. If you were to only target modern browsers with the Symbol type built in, you could in theory manually remove the logic to check for the Symbol type and recompile React. This would result in a tiny improvement in bundle size and execution speed.
We did test bundle sizes for both React and Vue, and saw less than 1% change in bundle size by compiling for only Chrome 75 compared to targeting all modern browsers.
Takeaways
Based on our testing, there can be a significant size savings by only targeting modern browsers.
If you need to support older browsers that typically have a bigger size penalty, like IE 11, it’s worth testing to see how much your bundle sizes increase, as there could be a significant penalty.
If your bundle size is significantly increased when supporting older browsers, one option is to serve different bundles to different browsers. You can serve a bundle with extra polyfills to browsers like IE 11 and Opera Mini, and a more streamlined bundle to current versions of Chrome or Firefox.