Home | Send Feedback

Self-host Polyfill.io

Published: January 15, 2018  •  javascript

In my previous blog post about Parcel I briefly mentioned polyfills and how to import them with Babel and TypeScript, and I also demonstrated an alternative way with Polyfill.io.

When you develop a web application and want to support older browsers but also want to use the latest browser features, you need a transpiler that rewrites the code into ES5 code, and you need to include polyfills. One common approach with polyfills is to bundle them together with the application. The problem is that this increases the bundle size, and users with modern browsers have to download code that their browser does not need.

An alternative is the Polyfill.io service. It's a service developed and maintained by the Financial Times. You send a request to the service and get back the requested polyfills, but only if your browser does not support the features natively.

In the simplest case you insert a script tag like this into your HTML page:

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

This returns the polyfill for the Fetch API. On most browsers, this returns an empty script, but on an older browser like IE11, this returns the polyfill code.

Polyfill.io analyzes the User-Agent HTTP request header to determine what code it has to send back.

You can also request multiple polyfills by listing them separated with a comma. This script tag requests the Fetch API and Promise polyfill.

<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch%2CPromise"></script>

The Polyfill.io website provides an URL builder, where you can select your required polyfills and get a polyfill.io URL that you can embed into your site.

One issue with Polyfill.io is that your application depends on a third party service you don't have any control over. This is maybe not a big problem when the application already depends on a lot of other external services.

But when Polyfill.io is the only external dependency, this might be an issue. Or you develop an in-house web application, where you have to support older browsers, and the employees only have limited or no access to the Internet.

Fortunately, this issue can be solved by installing Polyfill.io on your server. The source code is public available on GitHub, and it's written in JavaScript and Node.js. It should run on any platform where a Node.js runtime is available.

Installing

Here is a description of how you can install Polyfill.io on a Ubuntu Linux server. I tested this with Ubuntu 18.04.

Update all packages

sudo apt update
sudo apt dist-upgrade

Install Node.js

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs

Necessary tools for the build process

sudo apt-get install unzip gcc g++ python make

Download the polyfill.io source code and build it

Check the release page on GitHub to get the latest version number. Currently (January 2021), it's v4.36.0.

cd ~
wget https://github.com/Financial-Times/polyfill-service/archive/v4.36.0.zip
unzip v4.36.0.zip
rm v4.36.0.zip
cd polyfill-service-4.36.0
npm install
npm install run-s -D
npm run build

Now you can start the service with

./start_server.sh server/index.js

The service listens by default on port 8080. Open a browser and enter an example polyfill.io URL:

http://<ip_address>:8080/v3/polyfill.min.js?features=es2019

If you get back a response, everything is set up correctly. You can stop the service with CTRL+c.


User

Next, we move the build directory to /opt/polyfill-service, create an unprivileged user that runs the service (polyfill), and change the ownership of the directory to the new user.

cd ..
sudo mv polyfill-service-4.36.0 /opt/polyfill-service
sudo useradd -r -s /bin/false --home /opt/polyfill-service polyfill
sudo chown polyfill:polyfill /opt/polyfill-service -R

systemd

Next step is to configure systemd so that the service automatically starts when the server boots up

sudo nano /lib/systemd/system/polyfill.service

Paste the following code into the editor. I change the default port 8080 to port 3000. If that port is in use, change it.

[Unit]
Description=Polyfill Service
Documentation=https://polyfill.io
After=network.target

[Service]
Type=simple
User=polyfill
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/opt/polyfill-service/start_server.sh server/index.js
WorkingDirectory=/opt/polyfill-service
Restart=on-failure

[Install]
WantedBy=multi-user.target

Save and close the editor.

Reload systemd. Call this command every time you change /lib/systemd/system/polyfill.service

sudo systemctl daemon-reload

Now start the Polyfill.io service with systemd and check the status.

sudo systemctl start polyfill
sudo systemctl status polyfill

When systemd was able to start the service, you should see active (running) in the status output

If you want to stop or restart the service, you call systemctl with these options.

sudo systemctl stop polyfill
sudo systemctl restart polyfill

The service does not yet automatically start the next time the computer boots up. You have to enable it first.

sudo systemctl enable polyfill

You can check if a service is enabled with

sudo systemctl is-enabled polyfill

And if it's running with

sudo systemctl is-active polyfill

If you no longer want to start the service at boot time, you can disable it. This does not stop the service. It only removes it from the auto startup configuration.

sudo systemctl disable polyfill

And if you need to check the log file, you run this command

sudo journalctl -u polyfill

Proxy

If Polyfill.io is the only service running on this server, you could use the setup like this, maybe change the port to 80, but then you need to run the service as root.

When you have many services running on the same server you often have a reverse proxy installed. Most of the time, I use Nginx for this task. A simple setup only needs a proxy_pass directive

location / {
     proxy_pass http://127.0.0.1:3000/;
}

Polyfill.io with Babel

In this last section, I want to show you an example with a Parcel / Babel project and Polyfill.io (or your own, newly installed Polyfill.io server)

The application uses async/await, const, of loop, and the Fetch API. All features that are not available on an older ES5 browser like IE11.

import 'regenerator-runtime/runtime';

(async () => {
  const response =
          await fetch('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
  const geojson = await response.json();
      
  for (const feature of geojson.features) {
    console.log(feature.properties.mag);
  }
})();

Babel transpiles the language features like async/await into ES5 code. The code that Babel generates for async/await depends on a module called regenerator-runtime that we have to import.

The transpiled code also depends on Promise and Symbol (for the of loop). These are all features that can be polyfilled. The Fetch API is also not available on IE11 but can be polyfilled too.

The Polyfill.io URL for this simple application looks like this

<script src="https://polyfill.io/v3/polyfill.min.js?features=Promise,fetch,Array.prototype.@@iterator">
</script>

When you build the project (npm run build), you see that the bundle has a size of about 8KB, and it works when you open it with IE11. Users on a modern browser don't have to download any polyfill. Although all users now have to make an additional blocking request to Polyfill.io or your server.

If you are interested in a way to send this request conditionally, depending on the supported features in the browser, see description in this blog post: https://golb.hplar.ch/2018/02/Conditionally-load-polyfills.html

You find the source code for this project on GitHub: https://github.com/ralscha/blog/tree/master/parcel/polyfill-io