Polyfill.io

Authoring polyfills

Polyfills are located in the /polyfills directory and organised by feature name.

Naming

Polyfill directories should be named after their main JavaScript global, and nested subdirectories should be used to descend through objects, e.g. /polyfills/Array/prototype/forEach should be used to hold the polyfill for Array.prototype.forEach.

Where a polyfill exposes multiple globals, consider splitting it if possible, but if that's not practical, name the polyfill after the most well known of the globals.

If a feature has no JavaScript API, e.g. support for CSS styling of HTML5 elements, it should be given a descriptive name prefixed with a tilde (~), ie. ~html5-elements.

Files

Each polyfill directory (and subdirectory) is structured like this:

  • polyfill.js: Code to apply the polyfill behaviour. See configuration.
  • detect.js: A single expression that returns true if the feature is present in the browser (and the polyfill is therefore not required), false otherwise. Can be an IIFE. If not present, polyfill cannot be gated (wrapped in a feature-detect). Must not have a trailing semicolon.
  • tests.js: A set of tests written using mocha and proclaim, to test the feature. See test authoring guidelines.
  • config.json: Required. A config file conforming to the spec below

Configuration

The config.json file may contain any of the following keys:

  • browsers: Object, one key per browser family name (see browser support), with the value forming either a range or a list of specific versions separated by double pipes, identifying the versions to which the polyfill should be applied. We support all valid node-semver ranges, but discourage the use of the <= operator as it tends to cause confusion.
  • aliases: Array, a list of alternate names for referencing the polyfill. In the example Modernizr names are explicitly namespaced.
  • dependencies: Array, a list of canonical polyfill names for polyfills that must be included prior to this one.
  • license: String, an SPDX identifier for an OSI Approved license (Or CC0 which is GPL compatible)
  • repo: String, the URL of the repository containing the canonical version of the polyfill, of which the version stored in the polyfills library is a copy.
  • spec: String, the URL of a document that is the canonical technical definition of the feature (ie. the spec, preferably with fragment referencing the specific heading)
  • docs: String, the URL of an online resource with the most helpful description of the feature for developers (e.g. MDN)
  • notes: Array, Notices in markdown format that developers should be aware of, useful for deprecated features like String.prototype.contains, e.g. "This feature was deprecated due to poor existing native implementations, and replaced with String.prototype.includes"
  • build: Object, used to specify how the polyfill should be built.
    • minify: Boolean, whether the polyfill should be minified (default true). To speed up building of polyfills, this should be set to false for polyfill sources that are already minified.
  • test: Object, used to specify how the polyfill should be tested.
    • ci: Boolean, whether the polyfill should be tested in CI (default true).
  • install: Object, used to specify how to install the third-party polyfill into the polyfill-service.
    • module: String, name of the package on NPMJS to install. Must be available in the project's dependencies (so add it to dependencies collection in package.json)
    • paths: Array, paths of the files inside the package to combine together to create the polyfill. If not included, defaults to the packages default entry point.
    • postinstall: String, path to a javascript file to execute after creating the polyfill. This can be used to create any helper files for the polyfill.

Example:

{
    "browsers": {
        "ie": "6 - 9",
        "firefox": "<=20",
        "opera": "11 || 14",
        "safari": "<=4",
        "ios_saf" "<=6",
        "samsung_mob": "*"
    },
    "aliases": [
        "modernizr:es5array"
    ],
    "dependencies": [
        "Object.defineProperties",
        "Object.create"
    ],
    "license": "MIT"
}

A request from IE7 would receive this polyfill, since it is targeted to IE 6-9. It may also receive polyfills for Object.defineProperties and Object.create, since those are dependencies of the polyfill in this example, if those polyfills also apply in IE7.

Authoring guidelines

We are always glad to accept contributions of original polyfills as well as copies of third party polyfills maintained elsewhere. It's important to distinguish between these, because:

  • original polyfills are those where the polyfill service is the canonical source of the code. They do not have a repo or license property in their config. Original polyfills:
    • inherit the license terms of the polyfill service project as a whole, and all contributions are accepted subject to the committer's agreement to our contribution terms;
    • can be modified, tweaked and improved within the polyfill service repo;
    • must not be wrapped in a feature detect (this is a runtime option);
    • must not contain any polyfills for features other than the one it is intended to provide. For example, many polyfills depend on Object.defineProperty, but must not embed a polyfill for it
  • third party polyfills are maintained elsewhere can be identified by the presence of a repo property in their config. Third party polyfills:
    • should be installed using the install directive in config.json (see Configuration)
    • must not be modified from their original source (even stylistically) except where we have to in order to make the polyfill work in the context of the polyfill service (and where that modification doesn't make sense to the polyfill outside of the polyfill service). All other desired changes must be made upstream;
    • may have their own license terms, in which case a license property must be present in the config.json (which must be CC0, MIT, BSD or the URL of a compatible license);
    • may contain embedded polyfills for things for which we already have polyfills in the service, and in general, these should be left as they are in order to maintain sync with the upstream repo, even though we could make the polyfill smaller. Ideally, we should encourage the polyfill author to remove the embedded dependencies;
    • may contain a feature detect. Ideally they wouldn't, because the service will wrap the polyfill in a detect, but we'll tolerate a detect in order to maintain sync with the upstream source, provided that it is strict enough to fail in the case of broken implementations, as well as missing ones.

In addition, whether original or third party, if the polyfill targeting is open ended (ie it targets current versions of browsers), it must only make changes that create conformance with a published specification. If it is targeted only at older versions of browsers, it may be OK to exhibit any behaviour that is likely to satisfy the needs of applications that depend on the feature. For example, our devicePixelRatio polyfill in very old browsers simply returns 1 without actually measuring anything.

Binding to global scope

Polyfill bundles produced by the service are wrapped in the following IIFE:

(function(undefined) {
	// Polyfills go here
).call('object' === typeof window && window || 'object' === typeof self && self || 'object' === typeof global && global || {});

This provides a safe this variable to which polyfills may attach properties that they want to add to global scope. Prefer this to attaching to window or self, which may not always be what you think they are.

Feature detects

Since polyfills are loaded in an environment with a this value guaranteed to be an object, detects should lean heavily on the in operator and build from this, e.g.:

'navigator' in this && 'geolocation' in this.navigator

Objects defined as part of ECMAScript and present in all browsers above our baseline may be assumed to be present, so this is fine:

'imul' in Math

Update tasks for 3rd party polyfills

If a polyfill comes from a third party source, you can automatically install it by creating the appropriate install section in the config.json file. You should avoid committing third party polyfill sources into the polyfill service. If the third party source needs to be modified from the upstream form, and we cannot push that modification upstream, you can create a postinstall task to complete whatever additional work is needed. This task should be in a JavaScript file that is conventionally called update.task.js and lives alongside the polyfill.js and config.json files. It must complete its work synchronously. Example:

'use strict';
cost fs = require('fs');
const srccode = fs.readFileSync('./polyfill.js');

// ... do any required source modification

fs.writeFileSync('./polyfill.js', srccode);

Sometimes changes are required (or desirable) to support the format of polyfills needed by the polyfill service, for example:

  • Private dependencies: some polyfill authors will create polyfill collections that abstract common tasks into shared functions that are not themselves public web platform APIs. The polyfill service supports this via private polyfills (which are prefixed _), and will import and declare them in the parent scope so that the polyfill can consider them to be globals. Polyfills may therefore need to be amended to remove import or require statements (or to provide an import or require implementation that will satisfy them), and to provide the necessary dependency via other means (either as a separate private polyfill or by inlining it)
  • Embedded polyfills: some polyfills include code that fills more than one feature. For example, classList polyfills typically also polyfill DOMTokenList. This should be a dependency relationship expressed using the config.json metadata, rather than conflating two polyfills into one.
  • Feature detects / guards: most polyfills contain feature detects to ensure that they do not take effect if the feature is already present. Ours do not, because the feature detect is stored separately in detect.js and combined with the polyfill source at runtime. If possible, a polyfill should be imported without embedded detects.

Browsers

The short names should be used in the config.json to configure the browser support using the browsers key.

Short nameUser Agent NameBaseline
ieInternet Explorer / Edge / Edge Mobile>=7
ie_mobInternet Explorer / Edge Mobile>=8
chromeChrome*
ios_chrChrome on iOS>=4
safariSafari>=4
ios_safSafari on iOS>=4
firefoxFirefox>=3.6
firefox_mobFirefox on Android>=4
androidAndroid Browser>=3
operaOpera>=11
op_mobOpera Mobile>=10
op_miniOpera Mini>=5
samsung_mobSamsung Internet>=4
bbBlackberry>=6

Browser baselines

The polyfill service does not attempt to serve polyfills to very old browsers. We maintain a movable baseline of browser support, which is shown in the table above and configured in the UA module. If a request for a polyfill bundle comes from a UA that is below the baseline or unknown, the response will be something like this:

 * UA detected: ie/5.5.0 (unknown/unsupported; using policy unknown=ignore)
 * Features requested: Promise
 *
 * Version range for polyfill support in this family is: >=6

In practice this means that in some cases, where polyfills have configurations targeting all versions of a browser (e.g. "ie": "*"), the polyfill will actually only be available in versions of that browser above the baseline (unless overridden using the unknown query string argument).

This feature is intended to allow simpler testing and maintenance of the service, so that the general baseline can be moved forward without having to update every polyfill config individually.