Polyfills are located in the /polyfills
directory and organised by feature name.
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
.
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 belowThe 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.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 module 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.
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:
repo
or license
property in their config. Original polyfills:Object.defineProperty
, but must not embed a polyfill for itrepo
property in their config. Third party polyfills:license
property must be present in their config (which must be CC0, MIT, BSD or the URL of a compatible license);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.
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.
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
If a polyfill comes from a third party source, it's a good idea to write an update task that can be run to import the latest version of the polyfill code from its master repo. An update task is called update.task.js
and lives alongside the polyfill.js
and config.json
files. It must export a function that accepts the grunt
object, and currently must complete its work synchronously. Example:
'use strict';
var path = require('path');
module.exports = function(grunt) {
// ... download/import latest source here
// ... do any required source modification
var newSource = 'test';
grunt.file.write(path.join(__dirname, 'polyfill.js'), newSource);
};
Often changes are required (or desirable) to support the format of polyfills needed by the polyfill service, for example:
_
), 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)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.detect.js
and combined with the polyfill source at runtime. If possible, a polyfill should be imported without embedded detects.The short names should be used in the config.json
to configure the browser support using the browsers
key.
Short name | User Agent Name | Baseline |
---|---|---|
ie | Internet Explorer / Edge / Edge Mobile | >=7 |
ie_mob | Internet Explorer Mobile | >=8 |
chrome | Chrome | * |
ios_chr | Chrome on iOS | >=4 |
safari | Safari | >=4 |
ios_saf | Safari on iOS | >=4 |
firefox | Firefox | >=3.6 |
firefox_mob | Firefox on Android | >=4 |
android | Android Browser | >=3 |
opera | Opera | >=11 |
op_mob | Opera Mobile | >=10 |
op_mini | Opera Mini | >=5 |
samsung_mob | Samsung Internet | >=4 |
bb | Blackberry | >=6 |
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.