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 .
<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} → ${JSON.stringify(groups)}`);
</script>
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.
{
"targets": "baseline widely available",
"presets": ["@babel/preset-env"],
"plugins": [["polyfill-corejs3", { "method": "usage-global", "version": "3.50" }]]
}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();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.
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 ?? '-');
}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.