DEV Community

mk668a
mk668a

Posted on

Your polyfills are dead. Here's a robot that buries them.

The problem: polyfills never leave

A polyfill is a deal you make with the past. A browser doesn't support some
feature yet, so you npm install a shim, ship it, and move on. The deal is
supposed to be temporary — but nobody ever collects the other half.

Years pass. The feature ships in every browser. The polyfill is now pure dead
weight: it bloats your bundle, slows your installs, and adds one more link to
your supply-chain attack surface. And nothing tells you to delete it.

  • Renovate / Dependabot dutifully bump the polyfill's version forever. They keep the corpse fresh; they never tell you it's a corpse.
  • Linters flag some redundant dependencies, but they don't act, and they don't know whether the underlying feature is actually safe to rely on yet.

So @oddbird/css-anchor-positioning, @ungap/structured-clone,
array.prototype.flat, and a dozen friends quietly live in your package.json
for years after they stopped doing anything.

The signal: Web Platform Baseline

The missing piece is a trustworthy, dated answer to "is this feature safe to
use natively yet?" That signal now exists: Baseline.

The web-features dataset
tags each web feature with a Baseline status. The one that matters here is
Widely available (status.baseline === "high") — roughly 30 months after
the feature reached every major browser engine. Once a feature is Widely
available, the polyfill that shimmed it is, by definition, redundant.

That's a calendar-driven trigger: a polyfill becomes removable on a specific
day, whether or not anyone is paying attention.

The tool: baseline-polyfill-pruner

baseline-polyfill-pruner watches that signal for you and removes the dead
polyfills it finds. It runs as a CLI, or — better — as a GitHub Action that
opens a pull request the moment a polyfill becomes removable.

How it decides

A dependency becomes a removal candidate only when both of these are true:

baseline-high(feature)  AND  browserslist-targets-all-support(feature)
        →  polyfill is dead weight  →  removal candidate
Enter fullscreen mode Exit fullscreen mode
  1. Baseline says so. The feature the polyfill maps to is Baseline Widely available in the web-features dataset.
  2. Your targets agree. Your project's own browserslist targets all support that feature natively.

Step 2 is the important one. "Widely available" is a global statement about
the web. But if your browserslist still targets ie 11 or an ancient
Safari, the polyfill is still doing real work — so the tool keeps it.

The guiding principle is "when in doubt, don't remove." A missed removal is
harmless (you keep a dependency a little longer). A wrong removal breaks
someone's build. The whole design is biased toward the safe mistake.

How it maps polyfills to features

A hand-curated registry maps ~30 well-known polyfills (the es-shims
ECMAScript ponyfills, plus DOM/CSS shims like intersection-observer,
dialog-polyfill, whatwg-fetch, and @ungap/structured-clone) to their
web-features ids.

Crucially, every id in the registry is checked against the live dataset in
CI.
If a feature gets renamed or someone fat-fingers an id, the build fails —
instead of silently mis-flagging your dependency. It's quality over quantity by
design.

Using it

As a CLI

baseline-prune --diff           # report removable polyfills (default, no writes)
baseline-prune --fix            # remove them from package.json + list import sites
baseline-prune --json           # machine-readable report
baseline-prune --cwd ./app      # target a specific project root
Enter fullscreen mode Exit fullscreen mode

A dry run looks like this:

would remove  array.prototype.flat  — array-flat is Baseline Widely available (since 2022-07-15)

✓ 1 removable polyfill(s) found.
   Run with --fix to apply.
Enter fullscreen mode Exit fullscreen mode

--fix removes the dependency from package.json — preserving your
indentation, key order, and trailing newline — and then hands you a checklist of
every import site that still references it:

removed  array.prototype.flat  — array-flat is Baseline Widely available (since 2022-07-15)

⚠  2 import site(s) still reference the removed package(s).
   Remove these manually, then run your build/tests:
   - src/list.js:2  (array.prototype.flat)
   - src/list.js:8  (array.prototype.flat)
Enter fullscreen mode Exit fullscreen mode

Note what --fix doesn't do: it never rewrites your source code. Automatic
import-site rewriting (an AST codemod) is deliberately out of scope. A wrong
edit to your source breaks builds and burns trust — so the tool edits the
manifest and lets you handle the import lines.

As a GitHub Action (the point)

The real value is automation. A polyfill becomes removable on the day Baseline
flips, and no human remembers to re-check. So put it on a schedule:

# .github/workflows/baseline-prune.yml
name: Baseline polyfill prune
on:
  schedule:
    - cron: "0 9 * * 1" # Mondays
  workflow_dispatch: {}
permissions:
  contents: write
  pull-requests: write
jobs:
  prune:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: mk668a/baseline-polyfill-pruner@v1
Enter fullscreen mode Exit fullscreen mode

Every Monday, the Action runs the engine, applies --fix, and — if it finds
anything — opens a PR titled "Drop N polyfills Baseline reports as widely
available"
with a rationale table and the import-site checklist in the body.
You review it and merge with confidence.

Why this didn't already exist

Tool What it does The gap
eslint-plugin-depend Lints for redundant deps Lint-only, no autofix, no Baseline
module-replacements-codemods Codemods deps → native Doesn't key off Baseline status
Renovate / Dependabot Bump versions Never say "this dep is removable"

Each piece existed. The combination — a live Baseline trigger plus an
automatic removal PR
— is the thing none of them do. That combination is the
whole idea.

One design note worth stealing

baseline-polyfill-pruner uses no LLM at runtime. web-features is a
static, versioned dataset, and the removal decision is deterministic date
arithmetic plus a browserslist check. AI helped build the tool, but the
shipped binary is boring, predictable, and offline — exactly what you want from
something that edits your package.json on a cron schedule.


baseline-polyfill-pruner is MIT-licensed. Source:
github.com/mk668a/baseline-polyfill-pruner.

Top comments (0)