Abstract

Polyfills are a valuable part of Web architecture, because they promote the adoption of new features during the implementation process. However, polyfills can also create problems for standardization efforts and therefore for the future of the Web. This document defines the risks, benefits, and role of polyfills on the Web, and proposes best practices for implementors, spec editors, website developers, polyfill authors, and operators of polyfill distribution platforms.

For deeper background, see relevant TAG mailing list thread and F2F meeting minutes.

Status of This Document

This document has been produced by the W3C Technical Architecture Group (TAG).

The TAG approved this finding at its February 2017 face to face meeting. Please send comments on this finding to the publicly archived TAG mailing list www-tag@w3.org (archive).

1. Nomenclature: What is a polyfill?

A polyfill is a piece of JavaScript code that is intended to exactly simulate a native, interoperable feature of the web platform, for the purpose of allowing website developers to use modern web features in older browsers.

The term polyfill was coined by Remy Sharp in the 2009 book Introducing HTML5 (p276), and differentiated from shim:

What makes a polyfill different from the techniques we have already, like a shim, is this: if you removed the polyfill script, your code would continue to work, without any changes required in spite of the polyfill being removed.

Since then, the term prollyfill has been used by some to refer to code which implements features that are yet to be sufficiently standardized to be considered part of the web platform. Ponyfill is occasionally used to refer to code which implements standard platform features but in a private scope. Other terms such as nottifill, shiv, sham, shim, library and patch also exist and are used with varying degrees of popularity and shared semantics.

We find that although the terms library and polyfill are the most well recognized of these, polyfill's etymology is non-obvious and is in part culturally specific to the UK. For the avoidance of confusion, we use the following terms in this finding and offer them as a useful reference:

2. Feature life cycle and the role of polyfills

From a practical standpoint, the development of web platform features goes through a number of distinct phases:

  1. Idea discussion
  2. Incubation
  3. First native implementation (as a trial / behind flag)
  4. Specification
  5. Multiple interoperable implementations
  6. Universal support

The time lag between the first interoperable implementations (5) and fully universal support (6) can be long (occasionally as long as a decade). Polyfills can often effectively bridge this gap (between stages 5 and 6 above) and allow site developers to write solutions that assume universal support.

2.1 Risks of premature polyfilling

Early, speculative polyfills help shape the standards process. However, any JavaScript library that defines a property of the global object or extends a prototype of a global constructor using a proposed or generically useful name, risks creating problems for the development of the Web if that library becomes widely used, prior to the standardization and implementation of the feature it seeks to create or emulate.

Polyfill authors can avoid these problems if their version of the feature can be used under a custom name, regardless of the existence of any native implementation of the same or similar feature under a different name. However, many site developers will canonicalize the name of a library by deferring to a 'fantasy' native implementation under a name that might be standardized in future, or which is standardized but only available in some browsers, e.g.:

requestAnimationFrame = requestAnimationFrame || webkitRequestAnimationFrame || rafEquivLibrary;

Following this pattern sets up an assumption that the standard native implementation, when available, will be functionally identical to a vendor-prefixed implementation or speculative polyfill, which may not be the case.

2.1.1 Tipping point

It is useful to recognize a point in the lifecycle of a feature at which the definition of the feature is stable enough that polyfills no longer present significant risks to the evolution of the specification, and indeed are now essential to help the feature achieve a more rapid rollout.

There is no common agreement on how to identify the tipping point, and it may indeed differ depending on the circumstances and type of feature. We find that the following indicators are relevant:

  • Multiple implementations: are there multiple interoperable implementations in shipping browsers?
  • Consensus: Is there broad consensus on the detail of the feature with no unresolved objections that would change the API interface or observed behavior?
  • Test suite exists: Is there a comprehensive test suite that passes for native implementations?
  • Intent to implement: Is there a publicly declared intention to implement the feature based on the current specification from a major browser vendor?

It may be difficult for polyfill authors to determine whether a feature has achieved consensus, and in cases where it's unclear, authors should consult the chairs or editors of the feature specification. In many cases this can be done by opening an issue in a public issue tracker, if the specification effort is using one, or otherwise by sending email to the appropriate mailing list. A lack of response should be interpreted as an indication that a feature has not passed the tipping point.

2.1.2 Other potential concerns

As the number of web platform features increases, the amount of polyfill code required to make website developers' application code run in older browsers may become extremely large - in some cases exceeding the size of the application code that depends on it. This has implications for performance, both in terms of parsing the polyfill code, and the time required to download it.

Consideration should be given to whether excessively large polyfill bundles place a punitive cost burden on users with the least ability to pay that cost: those with older devices which may be on metered connections paying for data in very small increments.

Polyfills are often written by individuals, and may not have the infrastructure or support network of a major project or organization, leading to potential security concerns. These concerns are amplified if the polyfill acquires wide adoption, which may leave many unconnected sites vulnerable to the same security exploit.

3. Advice for polyfill authors

Polyfill authors can mitigate the concerns outlined above. Following a set of best practices will help to ensure that the polyfill promotes the adoption of the feature, rather than standing in the way of its standardization.

3.1 Encapsulate as a module or UMD

Use standard mechanisms for encapsulating your polyfill, such as ECMAScript modules or commonJS. If not defining an ES module, use a universal module declaration or other broadly compatible module API to make your polyfill compatible with multiple module loaders.

Don't pollute the global scope by creating or extending global objects inside a module that is intended to be consumed via a module loader.

3.2 Use smart distribution

Choose a platform on which to publish your polyfill from which developers can easily consume it in a way that keeps up to date with changes to the original source. For example, use a module registry and consider hosting the polyfill on a reputable service from which it can be directly loaded in the browser.

3.3 Don't squat on proposed names in speculative polyfills

If a feature has not passed the tipping point, do not squat on the proposed or common name (whether it's a property of the global object or of a global constructor/prototype). Consider prefixing or changing the proposed name, or appending 'polyfill'. For example, if an upcoming feature is likely to be called window.Foo, consider attaching your polyfill to window.FutureFoo or window.FooPolyfill.

Avoid incredibly short or obviously generic names, such as $, root or web.

If the feature has passed the tipping point, and your polyfill is spec-compliant, it's OK to attach to the standard name.

3.4 Pass web platform tests, if they exist

Attempt to pass Web Platform Tests for your polyfill and advertise the results, as a way to help potential consumers of the polyfill more effectively evaluate the risks associated with using the polyfill.

3.5 Detect and defer to native implementations

For features that have reached the tipping point, where the polyfill registers the same name as the native feature, include code to detect a native implementation, and if one exists, defer to it. Where a native feature exists but is incomplete or buggy, consider using as much of it as possible and correcting only the deficiencies.

When a native implementation is available, consider throwing a warning to the console to tell the site developer that they loaded a polyfill unnecessarily.

For features that are yet to reach the tipping point, speculative polyfills should not detect or defer to native implementations, and should not use the same name as a native implementation.

3.6 Work with spec editors

If practical, collect information about polyfill usage, developer experience, or problems with polyfills conflicting with or behaving differently to a native implementation - and send that information to the people developing the specification or native implementations.

4. Advice for website developers

Website developers should ensure that the most up to date best practices for performance and security are followed when using any script on a website. In addition, they can achieve maximum benefits from polyfills by following these specific best practices.

4.1 Understand stability of both feature and polyfill

Use the lifecycle guidance above to understand whether the feature has passed the tipping point, and can be considered stable. When selecting a polyfill, look for signs of quality such as:

4.2 Don't use speculative polyfills that defer to native implementations

If a feature is yet to reach the tipping point, using polyfills that squat on the proposed name for the feature is highly inadvisable, because when the feature is finalized, it might work differently to the polyfill you are using, and your website may break. If a polyfill author has followed good practice for pre-tipping point features by providing a polyfill with a non-conflicting name, don't undo this good practice by aliasing it to the proposed name.

Instead, use the speculative polyfill in private scope or under a custom name, wait until the feature has passed the tipping point, then update your code to use a polyfill that automatically defers to native implementations where they exist.

Example 4
import rafPolyfill from 'request-animation-frame';
rafPolyfill(function() {
...
});

4.3 Don't serve unnecessary polyfills

Shipping unnecessary bytes to users that don't need them wastes the user's time and data allowance, but performing feature detection first can delay the loading of necessary polyfills and adds the overhead of the loader itself. It is generally better to optimize for modern browsers, so performing efficient client-side feature detection and waiting for an extra script to load on older browsers is usually a good trade off. However, if the full set of polyfills you might need in the worst case constitutes a negligible overhead, then you could choose to serve the full set to all browsers.

Polyfill authors may choose to throw a warning if a polyfill is loaded when not needed, though care should be taken not to create unnecessary noise. If unnecessarily loading a particular polyfill creates a significant performance or security concern, a warning is appropriate.

4.4 Keep polyfills up to date

When incorporating polyfills into your codebase, create a mechanism that allows you to easily update the polyfill from source later. Updating polyfills regularly will help to ensure that browsers receiving the polyfilled behavior exhibit the same functionality as those in which you are using the feature natively.

4.5 Consider the slowest, least capable devices

Typically the devices and browsers that require the largest amount of polyfill code are also the least capable of parsing it efficiently, and the most likely to be on a metered and expensive internet connection. Alternatives to polyfilling on these devices include:

If you implement a baseline, detection of browsers that fall below the line must be based on robust feature detection, not proxy indicators that may be easier to measure but which will also exclude capable browsers.

4.6 Make use of caches

Serve polyfill code to your users in a form that can be cached locally in the user's browser. If available to you, make use of a CDN to cache the polyfill code as close to users as possible. Ideally, use the immutable directive in the Cache-Control response header.

5. Advice for library and framework authors

Some libraries and frameworks bundle polyfills to achieve the maximum browser compatibility and developer satisfaction. We find that the following best practices apply to library authors:

5.1 Consider whether to include polyfills

In deciding whether to ship polyfill-like code inside your library, you should consider questions such as:

5.2 Always encapsulate

Don't modify globals or native object prototypes. If you ship code as part of your library that emulates platform features, do so inside a closure.

5.3 Use native implementations when available

Don't lean on bundled polyfill code unconditionally. In browsers that offer a native implementation, use it.

5.4 Consider alternatives

If including a polyfill is not a good solution, consider alternatives such as:

6. Advice for polyfill distributors

If you run a service or project that promotes and distributes polyfills, you can help the web move forward by adhering to the following best practices.

6.1 Don't host or serve bad polyfills

When considering whether to distribute a polyfill, check whether it meets the best practices outlined in Advice for polyfill authors.

6.2 Update polyfills as specifications change

Keep the polyfills that you host up to date. If practical, push updates directly to your customers, otherwise encourage them to move to the latest version.

6.3 Don't bundle dependencies

If you offer a service that bundles multiple polyfills together, make sure people using the service are able to easily find out which polyfills are being included, and can exclude any polyfills for features that they have already polyfilled earlier or by another means. This is especially important if your service automatically includes polyfills for features that are dependencies of features requested by the user.

6.4 Make use of caches

Serve polyfill code to your users in a form that can be cached locally in the user's browser. If available to you, make use of a CDN to cache the polyfill code as close to users as possible. Ideally, use the immutable directive in the Cache-Control response header.

7. Advice for spec editors

If you are developing a new feature for the web, polyfills can be hugely beneficial in helping to roll out that feature.

7.1 Don't be constrained by what is 'polyfillable'

New web features need not be constrained by the need to enable polyfill implementations. However, all other things being equal, choosing a design that is more polyfill friendly is desirable.

7.2 Make your feature easily detectable

Ensure that the feature exposes detection hooks that can be effectively used by feature-detection code to determine whether a user agent offers a native implementation of the feature.

7.3 Work with polyfill authors

Whether before or after the tipping point, development of polyfills should be welcomed, provided that the polyfill author adheres to the best practices included in this finding. Help polyfill authors by sharing feature rationale and taking on feedback based on the polyfill implementation.