Ponyfilling

I manage a project that runs on nearly every production UI application. We recently ran into an issue where a polyfill had a bug that caused problems with several applications that were also polyfilling on the same page.

We decided to ponyfill our application rather than polyfill so we would discontinue polluting the global namespace.

What is polyfilling?

Polyfilling is the more common term. From MDN:

A polyfill is a piece of code (usually JavaScript on the Web) used to provide modern functionality on older browsers that do not natively support it.

For example, Internet Explorer does not support the String startsWith function. If you open up IE and run "abc".startsWith("a") in the console, you will get an error Uncaught TypeError: "abc".startsWith is not a function.

You can then polyfill this behavior by attaching code to the global scope, as MDN often provides in their documentation:

if (!String.prototype.startsWith) {
    Object.defineProperty(String.prototype, 'startsWith', {
        value: function(search, rawPos) {
            var pos = rawPos > 0 ? rawPos|0 : 0;
            return this.substring(pos, pos + search.length) === search;
        }
    });
}

Now we can run "abc".startsWith("a") and get true.

Now, how about a ponyfill?

One problem that can exist with polyfills, as previously mentioned, is that they affect the global scope. If there is a bug in a polyfill, it will then impact all applications on the same page using that polyfill.

Ponyfills come in to solve this problem by isolating the code that requires polyfills, rather than patching it for older browsers. Thus, our ponyfill code would look like this:

function startsWith(search, rawPos) {
    var pos = rawPos > 0 ? rawPos | 0 : 0;
    return this.substring(pos, pos + search.length) === search;
}

The content of the code is the same as the polyfill, except that it does not attach to the global String namespace.

With ponyfills, we were able to isolate our polyfilled code to our own application without impacting other applications on the same page.

Configuring Babel

This can be a little tricky to get exactly right with Babel, because the documentation does not expressly state some aspects of how to accomplish this correctly. These were the steps I ended up taking.

Install the proper runtime polyfills and the transformation plugin that keeps the code from polluting the global namespace.

npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime-corejs3

Update .babelrc to include the following configuration. Do not use the useBuiltIns or corejs options of @babel/preset-env as this will continue to add polyfills to the global namespace.

{
    "plugins": [
        ["@babel/plugin-transform-runtime", { "corejs": 3 }]
    ],
    "presets": [
        "@babel/preset-env"
    ]
}

And voila! Your application should no longer provide polyfills on the global namespace, but should instead be isolating the ponyfilled code.