A Bloatless Web

5 min readJul 3, 2018
Photo by Joel Sparks on Unsplash

Let’s face it already: we have a code size problem on the Internet.

We could split the issue in many ways, but what I’d like to specifically talk about today is … drums roll … JavaScript 🎉

( I know, you didn’t see that coming, isn’t it? )

Update: don’t miss the Universal Bundle Loader follow up!

We serve unnecessary code

A very common technique to create production Web bundles is to use babel-preset-env, allowing developers to choose target browsers, so that everything modern found during transpilation can be patched through runtime polyfills and their features detection, or plugins, when necessary.

This is all good and awesome, but it also means we transpile valid modern code to support less capable, often way older, browsers targets.

In few words, choosing any IE version as preset target, would blow the production code size, its parsing time, and worst of all, native performance for every modern browser, but let’s remember that modern, ever green browsers, are actually the common case, not the other way around.

The TL;DR of the current state is that we’re penalizing everyone on the Web, which doesn’t look like a long term wise solution to adopt.

JS breaks too easily

Another little issue we have with polyfills and transpiled code is that a tiny little issue might compromise a whole site. Differently from HTML and CSS, we all know JS is not fault tolerant, so that today, as example, I’ve tested core-js on IE8, the patch-it-all polyfill behind Babel, to find out a simple setTimeout(alert, 1000, 123) would break, and so would my page.

Now we should ask ourselves: is it really worth it to penalize every modern browser performance and features to support something that might be even broken and not under our control? (yes, I’m talking about transpilers)

Rising the Browser bar

The good part about features detection is that these address only guilty-old browsers and nothing else, but if we embed all the patches for those browsers in the production code, even most updated and fully specs compliant browsers would be affected by that bloat, including mobile devices.

An easy way to solve this is to use services such polyfill.io which theoretically should fix all your browser needs automagically, but another effective way to fix old browsers is to simply check for window.Reflect existence, since it’s one of the latest global namespaces ever introduced in modern JS, and also the only way to procedurally extend classes, in case we use those.

<!-- JS -->
<script>this.Reflect||document.write(
'<script src="https://unpkg.com/core-js-bundle"><\x2fscript>'
);</script>

Above script, on top of your page/application, would grant any browser that never heard about Reflect, would receive the whole core-js bundle, or any other polyfill alternative you want.

The little essential difference about common polyfilling approaches, is that modern browsers won’t ever even hit a network request, and won’t ever be affected by that document.write pretty nasty operation.

Older browsers? They all respect document.write legacy behavior since ever, so after that, any script needing modern features will be ready to go 🍾

Targeting ES2015 vs targeting ES5

Specially classes, but also much more, works out of the box in 88% of the browsers. The this.Reflect check is also the indication your browser can handle ES2015 code instead of dumbed down ES5 one.

var LEGACY = !this.Reflect;

Stick a script before everything else with such check and feel free to serve ES2015 to every browser that is not LEGACY.

Not only JS

We bundle a lot of JS polyfills these days, but I haven’t seen Babel transpiling DOM methods and features yet, or … is it a thing already?

Regardless, what I believe would be very handy for any website these days, is to have basic DOM Level 4 functionalities in, such as el.closest(...), or el.append(...stuff) or el.prepend(...) as well as normalizedrequestAnimationFrame and all others.

<!-- DOM -->
<!--[if lt IE 9]>
<script src="https://unpkg.com/ie8"></script>
<![endif]-->
<script>(document.head&&document.head.after)||document.write(
'<script src="https://unpkg.com/dom4"><\x2fscript>'
);</script>

Above snippet would indeed bother only IE8- browser, and patch on the DOM side everything else that never heard about modern elements features.

And once again, the cool thing is that modern browsers won’t ever even need to do a network request to bring that stuff in!

DOM Extras too!

We all want to be able to write modern code and be sure it just works, right?

<!-- DOM Extras -->
<script>this.fetch||document.write(
'<script src="https://unpkg.com/unfetch"><\x2fscript>');
</script>

So, above script brings in fetch API only to older browsers, while modern browers will be just fine without that network request and extra code.

<script>try{new EventTarget}catch(e){document.write(
'<script src="https://unpkg.com/event-target"><\x2fscript>'
)}</script>

Above script will bring in an extendinble EventTarget without bothering most modern browsers. This is an admittedly super new thing, and yet some browser won’t need to download anything, so it’s a win in the long term.

<script>
(function(g,U){try{
if(new g[U]('q=%2B').get('q')!='+'||new g[U]({q:1})!='q=1')throw{};
}catch(e){g[U]=void 0;document.write(
'<script src="https://unpkg.com/url-search-params"><\x2fscript>'
)}}(this,'URLSearchParams'));
</script>

Above script will bring an eventually forced-patched URLSearchParams to browsers that won’t know about it or are not standard compliant.

And what about a way to finally deal with DOMContentLoaded at any time?

<script>
var DOMContentLoaded=new Promise(function($){document.addEventListener('DOMContentLoaded',$,{once:true})});
</script>

There we go, once previous scripts are executed on any browser, we are sure we can use a simplePromise and resolve it once for every browser, even IE8.

If you don’t believe me, check out this page or its repository locally.

What about CSS ?

At least regarding the box-sizing, we are covered down to IE8.

<!-- CSS -->
<style>
html{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
*,*:before,*:after{-webkit-box-sizing:inherit;-moz-box-sizing:inherit;box-sizing:inherit;}
</style>

but when it comes to addressing, not through media queries, CSS files to include in our page, what could we do?

Well, the CSS.supports(…) API would be super handy to do that, isn’t it?

<script>
document.write('<link href="' + (
this.CSS ? (
CSS.supports('--O:O') ?
'unicorn' : // modern browsers with variables
'basic' // relatively modern browsers
) :
'legacy' // every IE version ever
) + '.css" rel="stylesheet">');
</script>

Above code would bring in a unicorn.css file for most modern and common browsers, a basic.css file that could be post-processed by Less, Sass, or any relatively modern, but static, CSS generator, while every IE from 11 to zero would have a specific fallback.

Use legacy to bring in what legacy needs

I’m not sure this is clear, or it has been stressed enough in this post, but legacy APIs will always work on legacy browsers, and these will fade away from most modern one that won’t be ever involved through these techniques.

That basically guarantees that our product can work, with all its compromises, in legacy browsers, but it will also grant the best experience to everyone else, which is the majority of 2018 users.

Also, if you want my opinion and you care about your business, stop targeting ES5 when you transpile your code, you are most likely unawhare you don’t need to ruin your source code that much!

--

--

Andrea Giammarchi
Andrea Giammarchi

Written by Andrea Giammarchi

Web, Mobile, IoT, and all JS things since 00's. Formerly JS engineer at @nokia, @facebook, @twitter.

Responses (2)