Polyfills

Polyfills and Feature Detection

Three tools keep new code working in older browsers. Feature detection (The Web Platform APIs) asks whether a capability exists. A polyfill adds a missing runtime API, such as Object.groupBy, written in older JavaScript. A transpiler such as Babel 41,624 rewrites new syntax, because no script can teach an old parser what ?. means. Baseline (Standards Bodies) tells you when each fallback can go: use Widely available features natively, give Newly available ones a polyfill or fallback, and treat Limited ones as progressive enhancements.

Loading polyfills only when needed

Test first, then load, so modern browsers pay nothing. The demo deletes Object.groupBy to imitate Safari 17.3 10 .

Conditionally loading core-js from a pinned, hashed URLHTMLLive
<ul id="out" style="font: 15px system-ui"></ul>
<script type="module">
  const show = (text) => out.insertAdjacentHTML('beforeend', `<li>${text}</li>`);
  delete Object.groupBy;                        // simulate an older browser
  const missing = !('groupBy' in Object) || !globalThis.Iterator?.prototype.map;
  show(`Object.groupBy before: ${typeof Object.groupBy}; polyfill needed: ${missing}`);
  if (missing) await new Promise((onload, onerror) => document.head.append(Object.assign(
    document.createElement('script'), { onload, onerror, crossOrigin: 'anonymous',
      src: 'https://cdn.jsdelivr.net/npm/core-js-bundle@3.50.0/minified.js',
      integrity: 'sha384-FFQ9vMUG5ncCF536DGix0yCvJeaR1Td99BfLaGw6OZDOA/jCgMOK3an7oNGZu4FJ' })));
  const groups = Object.groupBy([1, 2, 3, 4], (n) => (n % 2 ? 'odd' : 'even'));
  show(`Object.groupBy after: ${typeof Object.groupBy} &rarr; ${JSON.stringify(groups)}`);
</script>
Browser output of Listing 9.91
Browser output of 91

Pin the version and add an integrity hash: the polyfill.io service turned malicious in 2024 after changing owners (integrity). The full bundle weighs 273 KB minified (88 KB gzipped), so applications should import per-feature modules from a build step instead.

core-js with Babel

core-js 25,536 (github.com/zloirock/core-js (https://github.com/zloirock/core-js 25,536 ), MIT, about 25,500 stars, version 3.50.0) is the standard-library polyfill: one module per feature (es.object.group-by, web.structured-clone), esnext.* proposals, and a core-js-pure build that leaves globals alone. Its companion core-js-compat records the first browser version with a correct implementation of each module. Babel 8 (June 2026) removed useBuiltIns and corejs from @babel/preset-env; use babel-plugin-polyfill-corejs3.

babel.config.json: inject only the core-js modules the code usesJSON
{
  "targets": "baseline widely available",
  "presets": ["@babel/preset-env"],
  "plugins": [["polyfill-corejs3", { "method": "usage-global", "version": "3.50" }]]
}
app.js, compiled with Babel 8.0.5JavaScript
const orders = [{ id: 2, status: 'paid' }, { id: 1, status: 'open' }];
const byStatus = Object.groupBy(orders, (order) => order.status);
const ids = orders.values().map((order) => order.id).toArray().toSorted();
Output
import "core-js/modules/es.iterator.constructor.js";
import "core-js/modules/es.iterator.map.js";
import "core-js/modules/es.iterator.to-array.js";
import "core-js/modules/es.object.group-by.js";

The four imports precede the unchanged code. Browserslist now resolves baseline widely available to Chrome 121 1 , Firefox 123 555 and Safari 17.4, so toSorted() needs nothing while iterator helpers (Safari 18.4) do. Object.groupBy is injected even though Safari 17.4 has it, because core-js-compat trusts only Safari 18.0 and later. In libraries, use "method": "usage-pure" so you never patch your users' globals.

Modernizr

Modernizr 25,682 (github.com/Modernizr/Modernizr (https://github.com/Modernizr/Modernizr 25,682 ), MIT, about 25,700 stars), started in 2009, runs feature tests and exposes the results on a global Modernizr object and as <html> classes such as webp or no-webp. Its last release, 3.13.1, is from August 2024, and its README warns that the modernizr.com builder is outdated; build from npm 2,036 with modernizr.build(config, callback). @supports, CSS.supports() and in checks now cover most needs; Modernizr still helps where detection needs a real test, such as decoding a tiny WebP or AVIF image.

Baseline as a polyfill policy

The web-features 939 package (Apache 2.0, version 3.38.0) holds the data behind every Baseline badge, so scripts can query it.

Querying Baseline status with web-featuresJavaScript
import { features } from 'web-features';
const labels = { high: 'Widely available', low: 'Newly available', false: 'Limited' };
const ids = ['structured-clone', 'array-group', 'set-methods', 'iterator-methods', 'temporal'];
for (const { name, status: s } of ids.map((id) => features[id])) {
  console.log(name.padEnd(22), labels[s.baseline].padEnd(17),
    s.baseline_high_date ?? s.baseline_low_date ?? '-');
}
Output
structuredClone()      Widely available  2024-09-14
Array grouping         Widely available  2026-09-05
Set methods            Newly available   2024-06-11
Iterator methods       Newly available   2025-03-31
Temporal               Limited           -

Enforce the policy in CI with baseline-js/use-baseline (eslint-plugin-baseline-js), the use-baseline rule of @eslint/css, and stylelint-plugin-use-baseline. When a feature turns Widely available, as array grouping did on September 5, 2026, delete its polyfill.