These days we all know what polyfills are. It’s been over five years since Remy Sharp coined the term. A polyfill is usually a snippet of code that patches a piece of functionality that’s missing in some browsers. With ES5, polyfills became all the rage because you could instantly get access to functional Array.prototype
methods like .map
and .reduce
just by dropping in a file. There’s also entire bundles that patch most of ES5 for you to use in older browsers, such as es5-shim
. However, not all is peaches and cream.
When it comes to ES6, a flurry of problems turn polyfills into ineffective vaccines. For one, you simply can’t polyfill language features, such as arrow functions, generators, async
/await
(ES7), rest and spread parameters, classes, modules, etc. There are other features you *could* actually polyfill, such as Array.of
, Number.isNaN
or Object.assign
, because those don’t introduce syntax changes to the language – except that you shouldn’t.
Christian Heilmann argues that, sometimes, developers probe feature support by testing for some other feature that’s implemented most of the time alongside the feature we actually want to use. Under those circumstances, and considering you already have tools like Babel if you’d like to play around with ES6, I suggest you strongly avoid polyfills for any ES6 features, out of the ones that could be polyfilled.
As an alternative, you could use ponyfills instead.
Ponyfills
A ponyfill is almost the same as a polyfill, but not quite. Instead of patching functionality for older browsers, a ponyfill provides that functionality as a standalone module you can use. Let’s go to an example.
Here’s how your typical polyfill looks like – it was taken from MDN.
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
};
}
The equivalent ponyfill would be a module that exports the method below.
function trim (text) {
return text.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
They’re very similar, except a ponyfill doesn’t patch missing functionality others may be relying on for feature detection.
Polyfills or Ponyfills?
I think both have their merits. Polyfills are great for methods like String.prototype.trim
because they allow you to use the methods on String
instances. That gets even better with methods like .map
, and .reduce
which return this
, so chaining is great. Sure, you could always do reduce(map([1, 2, 3], twice), sum, 0)
instead of [1, 2, 3].map(twice).reduce(sum, 0)
, but there’s also the fact that many of your dependencies probably are assuming ES5 is on the table, so it’s useful to polyfill for the stuff that’s missing in older browsers.
When it comes to ES6 (or more complicated ES5 polyfills) however, the situation changes a little bit. Sometimes we can’t implement a solution that’s fully spec-compliant, and in those cases using a polyfill might be the wrong answer. A polyfill would translate into telling the rest of the codebase that it’s okay to use the feature, that it’ll work just like in modern browsers, but it might not in edge cases.
In that situation it’s better to use a ponyfill, because that way you won’t be polluting expectations about what. Only you will be leveraging the new functionality, and you are well aware of the limitations of that solution, so all is good with the world. Meanwhile, other pieces of code that are feature-detecting on the piece of functionality you’ve half-patched will continue to work by ignoring it as usual. Here, we can fall back to a great thing I’ve heard that I don’t hear often enough: “users browsing the web with shitty browsers are used to shitty experiences”.
In so many words, strive not to break expectations.
Have any questions or thoughts you’d like me to write about? Send an email to thoughts@ponyfoo.com. Remember to subscribe if you got this far!
Comments