DevEasy
Aug 21, 2022

How to load POLYFILLS only when needed

Problem:-

How to serve polyfills only to browsers that need them?

I know three ready-to-use approaches for that:

--- pollyfills.io
--- the module/nomodule pattern
--- the useBuiltIns option in @babel/preset-env

Pollifills.io

polyfill.io is a service that inspects the browser’s User-Agent and serves a script with polyfills targeted specifically at that browser.

With polyfill.io, you add a single script in front of your bundle:

<script src="https://polyfill.io/v3/polyfill.min.js?features=default"></script>
<script src="/bundle.min.js"></script>

and the script serves exactly the polyfills the visitor needs.

Points to consider:-
The polyfill.io script will add 50-300 ms to your Time to Interactive. The script is (obviously) hosted on a server different from yours, and loading stuff from a different server is costly. The browser will have to spend extra 50-300 ms to setup a connection to the server, and this means adding 50-300 ms to your Time to Interactive. (Well, unless you resort to self-hosting or complex CDN hacks.)
The polyfill.io script might add these 50-300 ms to your First Contentful Paint as well – if you put it into <head> without an async or a defer attribute.
In the (unlikely) event of a polyfill.io outage, your site will either get very slow, or will break in older browsers. The outage is unlikely because polyfill.io relies on a CDN and has never gone down so far; but keep this in mind

module/nomodule

module/nomodule is a pattern when you serve scripts for modern browsers with <script type="module">, and scripts for older browsers with <script nomodule>:

<!-- Full polyfill bundle for old browsers -->
<script nomodule src="/polyfills/full.min.js"></script>

<!-- Smaller polyfill bundle for browsers with ES2015+ support -->
<script type="module" src="/polyfills/modern.min.js"></script>

<!-- Bundle script. `defer` is required to execute
     this script after the `type="module"` one -->
<script src="/bundle.min.js" defer></script>

This pattern relies on the fact that old browsers – ones that don’t support ES2015 – will not load type="module" scripts – and will load nomodule ones. Which means you can use nomodule to serve ES2015 polyfills exactly to browsers that need them!.

So, in the snippet above:

---- the /polyfills/full.min.js script will only load in browsers that don’t support ES2015 and don’t recognize the nomodule attrubute – e.g., IE11;

---- the /polyfills/modern.min.js script will only load in browsers that support ES2015 and recognize type="module" scripts – Chrome 61+, Firefox 60+, Safari 10.1+;

---- the /bundle.min.js script will load in all browsers

Points to consider :-
Safari 10.1 is a quirk. It supports type="module" but doesn’t support the nomodule attribute. If you support this Safari version, you’ll have to work around that.
The module/nomodule patters draws a split only between ES5 and ES2015+ browsers. ES2016 and newer standards added a bunch of other polyfillable features like Array.prototype.includes() or Object.values(). You’ll have to serve their polyfills to all ES2015+ browsers – even though most of these browsers won’t need them.
type="module" scripts are always deferred. If you want to execute a type="module" polyfill before the regular bundle script, you have to add the defer tag to the bundle as well.

Babel’s useBuiltIns

@babel/preset-env has an option called useBuiltIns. With this option, you can make Babel cherry-pick polyfills for specific browsers:

// .babelrc
{
  "presets": [
    ["env", {
      // Specify browsers you’re targeting...
      "targets": "> 0.25%, not dead",
      // ...and either...
      "useBuiltIns": "entry",
      // ...or
      "useBuiltIns": "usage"
    }]
  ]
}

With useBuiltIns: "entry", Babel will replace the import of core-js – the most common polyfill library – with specific polyfills required for browsers you’re targeting. So if you’re targeting only the latest Chrome and Firefox, @babel/preset-env will strip unnecessary polyfills for you:

// Before → 293 polifylls bundled
import 'core-js';

// After → 87 polyfills bundled
import 'core-js/modules/es.array.unscopables.flat';
import 'core-js/modules/es.array.unscopables.flat-map';
// ...

With useBuiltIns: "usage", Babel will go even further and only add polyfills for methods you actually use:

// Before → no polyfills bundled
console.log([5, 6, 7].includes(5));


// After → with `targets: "IE 11"` → 1 polyfill bundled
import 'core-js/modules/es.array.includes';
console.log([5, 6, 7].includes(5));


// After → with `targets: "Chrome >=70"` → 0 polyfills bundled
console.log([5, 6, 7].includes(5));
Points to consider :-
useBuiltIns: "entry" is not very useful if you’re targeting old browsers, like IE 11. It might remove some polyfills, like Object.getPrototypeOf, but most of them will stay in the bundle and would still be downloaded by everyone.
If you use core-js 2, useBuiltIns: "usage" will fail to add some of the newer polyfills. For example, it won’t polyfill this code: [].flat(); because it won’t know that .flat()is a method that requires polyfilling. To solve this, upgrade to core-js 3 which includes the latest polyfills.
useBuiltIns: "usage" will not add polyfills for your dependencies – unless you pipe node_modules through Babel. So if you aren’t cautious enough, you might get runtime error in older browsers.
In some cases, useBuiltIns: "usage" will add excessive polyfills. For example, with this code:
import { myVar } from './myModule';
myVar.includes();

@babel/preset-env has no way of knowing whether myVar is an array or a string – so it’d bundle polyfills both for Array.prototype.includes and String.prototype.includes.

Summing UP:-

All three widely supported solutions have their benefits and drawbacks:

Wrap Up. Share this article and kindly bookmark this website to get the latest blog on full-stack. Till then Good bye, happy reading.

Thank you

Aamir

Aamir

A handful guide to make the developerslife easy.

Leave a Reply

Related Posts

Categories

© Copyright, 2023