Babel and UglifyJS Pains
Recently, I've been thinking about browser support for FrontierNav. Most browsers support native ECMAScript 2015 (ES6) code natively, but somewhat popular yet unmaintained browsers like Android 4.4, Internet Explorer 11 and Chrome 29 have been holding things back.
So, I decided to drop support for them and go full ES6 in production. I thought the ecosystem and build tools were ready. I was wrong.
Babel's recommended way to support ES6 or newer syntax in code is to use
babel-preset-env. You can configure this library to target a range of browsers and it will automatically apply the correct Babel Presets, Plugins and Polyfills for any ES6+ syntax those browsers don't support. By doing this, you reduce the amount of code transformations and general bloat.
The browser support is chosen using
browserslist, a common utility also used by other compatibility tools like
autoprefixer for CSS.
Before making any changes, I was using the defaults, which is roughly the last 2 versions of any known browser and any browser used by more than 0.5% of the tracked population.
> 0.5%, last 2 versions, Firefox ESR, not dead
It's worth noting that the tools that use
browserslist can only use a subset of this information.
babel-preset-env only checks for Chrome, Firefox, Safari, Android, Edge and Internet Explorer versions. From those, I found that:
- Chrome 29 is still used despite being ancient. This is because it's the first version for Android and no one updates their stuff when given a choice.
- IE 11 is dead weight at this point.
- Android 4.4's browser is the last Android-specific browser. All recent versions have shipped with Chrome or a vendor-specific one like Samsung Browser.
These 3 browsers together are holding back
babel-preset-env from dropping the vast majority of ES6 transformations. Things like
const and arrow functions. Since they were also barely a blip in my audience, I decided to get rid of them using this browserslist:
Simple right? Wrong.
The current stable version of
babel-preset-env is v1, which does not support the common ways of sharing browserslist configuration across tools.
I didn't realise this until enabling the
debug and noticing the mismatch between that
browserslist says and what
Turns out, I was reading the wrong documentation! The README on GitHub is for v7, the real documentation is on the Babel website. I wish this was made obvious so that I could save an hour.
The real configuration is in the
Does it work?
Yes! ... Does it work in production? No.
Now that the bundled code has ES6 syntax, we need to use
uglify-es. A version of
UglifyJS which supports such syntax. Now comes the next problem.
uglify-es you need to specify your target version. What is our target version? Well, I don't know. The whole point of
babel-preset-env is to support any ES6+ syntax on any given environment. It abstracts away the need to know what "version" is going out.
uglify-es targets an ES5 environment. So I gave it a shot. But I started getting all of these errors in Rollbar:
TypeError: invalid assignment to const `t'
TypeError: Attempted to assign to readonly property.
Looking at the output code, it's obvious that
uglify-es is trying to optimise code and causing naming conflicts across various scopes. Unlike
const doesn't like that.
I tried targeting ES6, I tried disabling various configurations, all with limited results. There are few issues filed related to this, it's all very complicated. Doesn't help that
uglify-es, despite being different from UglifyJS, is a branch and shares the same repo and issues tracker. To summarise it's organised:
- UglifyJS is the old UglifyJS
- UglifyJS2 is the new UglifyJS in the UglifyJS2
- UglifyJS3 is the new UglifyJS2 in the UglifyJS2
- uglify-es is an alternative ES6+ aware UglifyJS3 in the UglifyJS2
After a few hours, I decided to stop. What's the point? Yes, I can ship ES6+ code by doing this, but my deployment is only 100KB smaller and I'll be supporting less browsers. The amount of effort is not worth it.
Now I've rolled back, everything's back to what it was. A huge waste of time. At least I learnt something. The ES6+ ecosystem still has a long way to go. Everything's complicated, tooling goes out of alignment, documentation is all over the place and there's constant regressions.
I'll keep things as they are for now. I was thinking of using
babel-minify instead of
uglify-es since it's meant to be "ES6+ aware" too. But it looks kind of slow, the Webpack plugin doesn't look active so it might not work and I'm kind of burnt out from all of this anyway.