Building Polyfills: Web Platform APIs for the Present and Future (2014)
Part I. Building Polyfills
In this first part, we’ll explore the world of polyfills as they exist today. In Chapter 1, we’ll discuss the origin of polyfilling, and in Chapter 2 I’ll share some principles and practices for the polyfill developer to consider when building libraries for cross-browser compatability. In Chapters 3through 5, we’ll get our hands dirty and walk through building a polyfill for the HTML5 Forms specification. We’ll also cover testing and build workflows and the all-important subject of polyfill performance.
Chapter 1. The Current State of Web Polyfills
Like many things in the world of technology, the practice of polyfilling is far older than its name. And even though the term itself is a recent addition to the web platform canon, as long as we’ve had multiple browsers with varying and inconsistent implementations of web platform features—which is to say, always—we’ve had the practice of polyfilling in one form or another. Every developer who mucks with the string prototype to add a trim() function for oldIE is creating a polyfill, as is the developer who lovingly adds a homegrown window.addEventListner() function to IE8 in the hopes of simplifying her event management code. As a practice, polyfilling has been around for a while, but its naming coincides with a time in which its use on the Web exploded: the advent of HTML5.
Polyfilling: Past, Present, and Future
Much as the term Ajax was minted at a time when JavaScript and XHR apps were a daily occurrence, the practice of polyfilling was given a name at a time when developers were increasingly looking at HTML5 and thinking, “How can I get some of that in my site?” Part of the answer at the time was polyfilling and, in many ways, it’s been an adequate answer. For the last several years, polyfilling has allowed developers to target multiple browsers with new technologies, while not leaving those oldIE users behind. This is polyfilling’s past, and also its present. We still live in a world where the predominant use case for a polyfill is “filling the gaps” in a browser that doesn’t yet natively provide a feature or features that we need.
But were that the end of the story, I don’t think an entire book on polyfills would be very interesting. Thankfully, the story continues, and a subtle shift in how we look at polyfilling is taking place in the web community. Rather than simply building polyfills to modernize older browsers, a new class of library is starting to emerge. These libraries, sometimes called forward polyfills, or prollyfills, are created with the goal of trying out new ideas and unproven specifications on the Web. This can be something as simple as a library that adds classes to JavaScript in accordance with the ECMAScript 6 specification, or as complex as a full suite of libraries that provide advance support for Web Components, as is the case with the Polymer project.
It’s a powerful pattern, and this new class of polyfill is shifting the balance of power on the Web. Increasingly, these libraries are allowing us lowly web developers to emerge as not just authors of websites and applications, but also as architects of the web platform itself. It’s an exciting time to be a polyfiller.
What Is a Polyfill (and What Is It Not)?
In 2009, as he was working on the popular book Introducing HTML5, Remy Sharp conceived and coined the term polyfill to describe a practice that he’d seen increasingly applied by developers adopting HTML5 and CSS3 in their applications. In a post on his blog from 2010, Sharp defines the term as follows:
A polyfill, or polyfiller, is a piece of code (or plug-in) that provides the technology that you, the developer, expect the browser to provide natively. Flattening the API landscape if you will.
For those of you in the United Kingdom, the term polyfill might evoke images of Polyfilla, a paste that Britons use to cover cracks and holes in wall (we call this spackling in the United States). It’s a convenient image that happens to describe exactly what good polyfills strive to do: fill in the gaps and holes of browsers so that a developer can go to work on a smooth, consistent surface.
The term also has a more direct, deeper meaning if analyzed in parts, poly and fill. Fill denotes the hole in the browser that the library exists to address, and poly means that the problem can be solved by using any number of techniques. JavaScript might be the predominant polyfilling approach, but there are several examples of polyfills that fall back to plug-ins like Silverlight or Flash to provide functionality to the browser. Notable examples of these are the excanvas plug-in, which falls back to Silverlight to provide Canvas support in IE6–8 and mediaelement.js, which provides consistent HTML5 media support across browsers with Flash.
According to Sharp, the practice that we now refer to as polyfilling needed a new term because existing terms didn’t convey the key idea of replicating the API of native browser features by using JavaScript. Polyfills are distinct from both shimming and progressive enhancement in that regard. The term shim does describe a library or bit of code that adds features or functionality to the browser, and may even provide an abstraction that spans cross-browser inconsistencies, but these libraries often introduce their own, specialized APIs. jQuery, which was first introduced as a way to normalize Document Object Model (DOM) interactions across browsers, is a popular example of a shim.
Progressive enhancement and its cousin graceful degradation are also inadequate for this idea, as these terms imply building sites that either gain functionality with JavaScript, or that remain functional in the absence of JavaScript, respectively. Polyfills often depend on JavaScript in the absence of native browser functionality and, as such, neither of these terms seem appropriate.
When Remy Sharp conceived of the term polyfill, he wasn’t attempting to coin a new phrase for posterity. Instead, he was hoping to describe an increasingly relevant practice that would undoubtedly become even more important as HTML5 and the open Web continued to proliferate throughout the development world. The web development community needed a term to describe this burgeoning practice, and Sharp had one to offer. In spite of periodic assertions from corners of the community that the word should be changed, “polyfilling” has caught on. I don’t expect that it, or the practice that it represents, will go anywhere any time soon. Quite the opposite, in fact, which is why you hold this book in your hands.
Types of Polyfills
With a proper explanation of polyfills out of the way, let’s spend a few moments talking about types of polyfills. While the formal definition does have a pure, simple interpretation, polyfills, in reality, don’t always respect this definition to a T. In this section, we’ll look at some common “classes” of polyfills, as well as the pros and cons of choosing each approach when building polyfills of your own.
The Shim
A shim is not a polyfill, at least by definition, but is often considered a worthy member of the polyfilling conversation nonetheless. This is for two reasons:
1. People often use the two terms interchangeably.
2. Shims and polyfills share similar goals, in spite of their differing approaches.
A shim is a piece of code, typically JavaScript, that’s designed to add functionality into the browser that is not already present, or to bring a level of consistency to various browser implementations. Unlike the various polyfill types, shims do not map their functionality to a built-in or specified browser API, instead choosing to implement their own API that developers must learn and adopt in order to leverage provided functionality.
As stated previously, jQuery—specifically the library’s sizzle selector engine and event management functionality—is a classic example of a shim. Selecting an element from the DOM with jQuery requires a specific action on the part of the developer. For instance, consider the simple selector in Example 1-1.
Example 1-1. Using jQuery to interact with the DOM
$('#myTodoList').find('li.dueToday')
.css('background-color','yellow')
.end()
.find('li.overdue')
.css('background-color', 'red');
The preceding sample is doing a number of things, not the least of which is performing three separate element selections from the DOM. This code is guaranteed to work consistently across all browsers, from IE6 on up, and there’s nothing special I need to do to manage how jQuery performs this selection from one browser to the next. Internally, jQuery does manage a complex algorithm for DOM selection based on the browser, using HTML5’s querySelector/querySelectorAll syntax, if supported, or the classicdocument.GetElementById/document.GetElementsByClassName approach, if not. However, because all of this functionality is abstracted into a separate API from that specified by the W3C—as opposed to adding its functionality to the document object prototype—it fits more into the definition of a shim than that of a polyfill.
In spite of the fact that shims are not polyfills, they do still offer some advantages to developers. For starters, their opt-in nature means that developers leveraging their functionality aren’t doing so by accident. Instead they are choosing to learn a new API because the library provides needed functionality. Because a library-specific API is being used, developers are guaranteed not to override built-in browser functionality in an unexpected way, which can happen with pure polyfills. A developer adopting a shim (hopefully) knows what he’s getting himself into and has chosen that shim for a reason.
Another advantage to shims is that the library developer isn’t constrained by the specified API of built-in functionality. If the developer wishes to diverge from the API to improve the library’s interface for developers—for instance, the chainability of jQuery selectors and methods—she is free to do so without worrying about affecting or breaking a standardized API. With pure polyfills, respecting the API is vital, as we’ll discuss in Chapter 2.
While some see the opt-in nature of shims as an advantage, others disagree. To these developers, adopting a shim is akin to taking on technical debt that requires rework to remove. Imagine a developer who adopts a shim with the goal of gaining some as-yet unimplemented functionality in the browser. If, in the future, the browsers add this native functionality, the developer must remove or update the shim to offer the native experience—and concomitant performance gains—to the end user. As long as the shim remains in place, the end user will receive a less than ideal experience in their perfectly capable browser. Polyfills, on the other hand, tend to have a built-in answer to this problem.
The Opt-In Polyfill
The next polyfill type on our list is the opt-in polyfill. These libraries qualify as polyfills in the sense that they operate on standard APIs. However, they are opt-in because the developer must take action in order to leverage them in their apps.
As an example, consider PIE, a popular CSS3 polyfill. PIE, aka Progressive Internet Explorer, exists to provide CSS3 features like border-radius, box-shadow, and border-image to, you guessed it, IE6, 7, and 8. The library works in one of two ways. Developers can use the relevant CSS properties, as normal, and then opt into PIE by including a behavior property at the end of the CSS rule, as shown in Example 1-2.
Example 1-2. Activate PIE using CSS
#myElement {
background: #DDD;
padding: 2em;
-o-border-radius: 1em;
-moz-border-radius: 1em;
-webkit-border-radius: 1em;
border-radius: 1em;
**`behavior: url(PIE.htc);`**
}
This IE-specific line of CSS activates the PIE polyfill.
In this example, the opt-in happens when the browser’s parser encounters the behavior property. If the behavior property looks foreign to you, you’re not alone. This property, which is supported only in Internet Explorer 5.5 through 9, allows you to use CSS to add a script to a selector, with the purpose of implementing something Microsoft calls HTML components. The .htc extension on our PIE file is an HTML component-specific format that’s essentially markup and JavaScript, plus some additional vendor-specific elements that define the components themselves.
When loaded, PIE.htc uses the CSS properties defined in its loading selector—border-radius in this case—to fake those features using the HTC component. So, PIE is using a decade-old IE-specific hack in order to add support for CSS3 to IE6–8. That’s pretty clever! Not only that; it’s also efficient. Since other browsers don’t support the behavior property, its existence will be overlooked, and the HTC file will be neither loaded nor parsed by browsers that already support CSS3 properties.
If the thought of using DHTML to fake CSS3 support in IE isn’t your cup o’ tea, you can also use JavaScript to add PIE to your apps, as illustrated in Example 1-3. Of course, you’ll still need to define the CSS for border-radius, as well as include any of PIE’s dependencies, like jQuery.
Example 1-3. Activate PIE using JavaScript
Modernizr.load({
test: Modernizr.borderradius,
nope: 'PIE.js',
complete: function() {
if (window.PIE) {
// Select all elements with class 'rounded'
$('.rounded').each(function() {
PIE.attach(this);
});
}
}
});
In this example, we start by leveraging Modernizr’s built-in Modernizr.load capabilities to determine if the CSS3 border-radius property is supported. A key feature of Modernizr.load is the ability to conditionally load a script file based on the truthiness or falsiness of a test. When using polyfills, it’s important to consider the users who don’t need a polyfill just as much as those who do. Ideally, you never want to load a library that a user doesn’t need, and polyfills are no exception. With Modernizr.load, you can load libraries only when the browser requires them.
TIP
Hopefully, you’re familiar with Modernizr as a polyfill consumer. If not, I highly recommend learning the library since, as a polyfill author, you’ll need to be intimately familiar with the various ways developers perform feature detection before loading your library. That is, unless you do the feature detection for them, which we’ll discuss shortly.
Once PIE.js is loaded, the complete function is called. At this point, I opt in to PIE by selecting elements from the DOM via jQuery and passing each element into the PIE.attach function, which does the fancy corner-rounding for me. It’s a bit more code than the HTML Components approach, but still simple enough to implement.
Opt-in polyfills have a couple of advantages over shims. For starters, these libraries work against standard APIs, meaning that developers don’t have to learn a new API or write a bunch of needless boilerplate in order to use them. What’s more, the footprint of these libraries is relatively small and self-contained. When the developer no longer needs to support an opt-in polyfill, the offending code is easy to track down and remove.
On the other hand, opt-in polyfills do still introduce technical debt in the form of extra code, even if that code is often just a few lines. A line or two of extra code is all fine and good with example code, but imagine having to add opt-in support for a few hundred selectors across a large site. Now, imagine what it would be like to remove all of that opt-in code a few years down the road, and deal with the regression that would undoubtedly pop up. Doesn’t sound like much fun, does it? Thankfully, there are other approaches that are designed to be a bit more hands-off.
The Drop-In Polyfill
The next type of polyfill is the so-called drop-in, or pure, polyfill. I’ve chosen this name to indicate a polyfill that adheres to the API of the feature in question while requiring no additional configuration beyond a script include. When included, a drop-in polyfill goes to work by adding its functionality to the browser, typically via additions to JavaScript prototypes or globals such as window or document. Once the polyfill is included and parsed, the developer is free to rely on standard functionality without the need for additional feature detects or user agent interrogation.
Let’s take a look at a simple, yet appropriate example: String.trim(). This useful function is included in all modern browsers, but is notably absent from IE6–8. If you’re tasked with supporting these browsers, and you absolutely must trim your strings with a built-in function, you can add that needed functionality via a drop-in polyfill, as illustrated in Example 1-4, courtesy of Mozilla’s excellent MDN documentation.
Example 1-4. Creating a drop-in polyfill for String.trim()
if(!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s+|\s+$/g,'');
};
}
In this example, we’re checking to see whether the trim function exists on the String prototype, and if not, we add a new function that performs a simple RegEx replacement. To leverage this polyfill, a developer need only include it somewhere in her app before the first call to trim. Iftrim does indeed exist, our polyfill isn’t needed and thus, nothing happens. It may seem like a minor point, but it’s important to note that a good polyfill is aware of its execution environment and adds functionality only when needed. We’ll talk more about this in the next chapter.
The biggest advantage of the drop-in polyfill is hands-off adoption for the developer. Because the workflow for using this type of library is a single step—include the library in your app—this type of approach is quite appealing for many developers looking for a quick solution that takes them out of the feature-detection game.
That said, the biggest benefit of the drop-in polyfill can also be a weakness. A drop-in polyfill can mislead developers into thinking that the API they are looking to leverage is both fully supported and implemented in a similar manner as standard approaches taken by already-supporting browsers. If your polyfill deviates from the spec in any way, or behaves unpredictably, you run the risk of confusing or annoying the developers who rely on your tool. As a result, building drop-in polyfills is not for the faint of heart. Taking on this type of library necessitates a deep understanding of the relevant spec, as well as some knowledge of the inner-workings of compliant implementations in other browsers.
It also requires that you either support the entire API of a given feature, or be crystal clear which aspects you do and don’t support in your documentation. By definition, drop-in polyfills can be subdivided into two types: those that fully fill a complete feature API, and those that fill only a portion of that API. An example of the latter subtype is the excanvas polyfill, which supports much of the HTML5 Canvas API, but does not support the rendering of text via the standard fillText and strokeText APIs. For text, the developer must either avoid using these functions, or leverage an additional polyfill for this functionality. While a partial drop-in polyfill is still useful, it somewhat obviates the benefits of building this type of library because the developer still must feature-test for those aspects of the API not supported by your library. It’s not always possible or sensible to fully support a standard API, of course, but where possible, you should make every effort to do so when building this type of polyfill.
The Forward Polyfill
The last type of polyfill is an emerging type: the prollyfill. According to Alex Sexton, who coined the term, a prollyfill is “a polyfill for a not yet standardized API.” These types of libraries, also called forward polyfills, are unique in that they are not designed to fill existing standards gaps, as polyfills have done traditionally. Rather, these polyfills are designed to test new or emerging standards inside browsers.
The prollyfill pattern is becoming more and more important to web developers and standards authors alike because they create a feedback loop between developers, spec authors, and browser engineers that’s heretofore been unseen on the Web. Traditionally, browser standardization has been very top-down, and often developers have no opportunity to experiment with new APIs until these are implemented in one or more browsers. If you want proof that top-down standardization isn’t always the best approach, see XHTML 2.0, Microformats, Web SQL, Application Cache, or any number of standards that failed to gain traction once subjected to real-world use by developers.
Prollyfills bring a bottom-up, democratized process to the open Web by allowing developers to experiment with and iterate on candidate APIs before they move into stable browser implementations. Prollyfills can also emerge from new libraries and languages that weren’t formed with the goal of shaking up the world of web standards. Take CoffeeScript, for instance, as shown in Example 1-5:
Example 1-5. CoffeeScript’s “dash rocket” and fat-arrow function syntax
square = (x) -> x * x
cube = (x) => square(x) * x
Though CoffeeScript isn’t really a prollyfill, it’s a great example of how developer adoption of an idea can drive the standardization process, as evidenced by TC39’s acceptance of fat-arrow function syntax into ECMAScript 6, as shown in Example 1-6:
Example 1-6. Arrow function syntax in ECMAScript 6
let square = x => x * x;
let cube = x => square(x) * x;
Prollyfills are an important concept, and they are a big reason for my writing this book. A deeper discussion of these, the rationale behind them, and various approaches for building prollyfills can be found in the latter half of this book. In the meantime, let’s talk about why polyfills should and do still matter to web developers.
Why Polyfills Still Matter
In the current era of faster updates to browsers—including our favorite punching bag, Internet Explorer—it’s easy to dismiss polyfills as a passing fad that have offered little more than to inform the early days of HTML5 adoption. In some ways, this claim isn’t unfair. When polyfills first entered the developer consciousness, their goal was singular: easing the path to HTML5 adoption by reducing the amount of platform-specific code needed to leverage a new feature. Had the polyfilling pattern never grown beyond this scope, we probably wouldn’t be talking about them much these days.
And yet, polyfills remain part of our everyday frontend vernacular. I believe this is for a couple of reasons. For starters, for HTML5 and beyond (the open Web, if you will), browser vendors continue to adopt emerging technologies at differing paces. While some browsers tend to add features as early as possible for the purpose of developer testing and feedback, others prefer to iterate solely in the standards bodies, adopting features only as they move further through the process and mature. It’s outside the scope of this book and well beyond this author’s temperament to render judgment on which of these approaches is more ideal, but these differences are worth mentioning because they point to the continued importance of polyfills. As long as browser x implements a different set of emerging features than browsers y or z, the need for polyfills remains.
Another reason for the continued importance of the polyfill is the emergence of the prollyfill, as described in the last section. The prollyfill variation has appeared over the last year in response to an ever-growing number of developers desiring to get involved earlier in the standards process. Prollyfills are a tool of choice for developers looking to “extend the Web forward.” In the recent Extensible Web Manifesto, polyfills were described as the catalyst that aids in creating a “virtuous cycle” between web developers and the W3C’s standardization process:
Making new features easy to understand and polyfill introduces a virtuous cycle:
§ Developers can ramp up more quickly on new APIs, providing quicker feedback to the platform while the APIs are still the most malleable.
§ Mistakes in APIs can be corrected quickly by the developers who use them, and library authors who serve them, providing high-fidelity, critical feedback to browser vendors and platform designers.
§ Library authors can experiment with new APIs and create more cow-paths for the platform to pave.
— The Extensible Web Manifesto http://extensiblewebmanifesto.org
Over the last few years, polyfills have grown from an HTML5 adoption tactic to a popular library development pattern and, finally, a full-blown standardization strategy. As a polyfill developer, you may be building your library with one of these targets in mind. Regardless of whether your polyfill is practical, tactical, or strategic in nature, you’ll want to keep some guiding principles and practices in mind as you set out to build your library. We’ll discuss these in the next chapter.