Polyfills

polyfillsjsweb dev

Saturday, July 19, 20252 min 47 sec read

Polyfills

Recently, I had built a CLI tool called git-bird, which uses Google’s Gemini API to generate Git commit messages. I developed and tested it on my personal machine using the latest Node.js version (v22), and everything worked as expected.

Later, I installed the same tool on my work laptop to use it in my regular workflow. But as soon as I tried to use it, it threw an error: ReferenceError: fetch is not defined. This was unexpected, since the tool had worked perfectly on my machine. Looking at the stack trace, I saw the error was coming from a fetch() call made inside the Gemini integration.

I checked the Node.js version on the work laptop and realized it was running an older version, somewhere before Node 18. That explained the issue: Node.js introduced native support for fetch and related APIs (Headers, Request, Response) in version 18. Anything older doesn’t have these globally available.

Upgrading Node.js wasn’t an option. The work project was tightly coupled to that specific older version, and even attempting to run it with a newer version would break due to legacy dependencies and tooling. Since the environment was locked down for compatibility reasons, I needed to make my CLI tool work within those constraints without requiring changes to the system setup.

This led me to a concept I’d known in theory but hadn’t applied in practice: polyfills. A polyfill is a piece of code that adds support for a missing feature in a particular environment. In this case, I needed to polyfill fetch and its related objects so the tool could run reliably on both latest and older Node.js versions.

After some research, I found undici, an HTTP client maintained by the Node.js team that also provides a fetch implementation. It’s a well-supported and reliable way to backfill modern web APIs in environments where they don’t exist.

I added the following logic to the entry point of my tool to apply the polyfill conditionally:

import { fetch, Headers, Request, Response } from "undici";
 
if (typeof global.fetch === "undefined") {
  global.fetch = fetch;
}
 
if (typeof global.Headers === "undefined") {
  global.Headers = Headers;
}
 
if (typeof global.Request === "undefined") {
  global.Request = Request;
}
 
if (typeof global.Response === "undefined") {
  global.Response = Response;
}

This checks for each global API and assigns it only if it doesn’t already exist. That way, the tool works across different environments without interfering with native implementations in newer versions of Node.

The experience was a good reminder that assuming consistency across environments can lead to unexpected issues. It also highlighted that polyfills aren’t just theoretical concepts you read about, they’re practical tools that solve real-world problems. In my case, a simple missing feature like fetch in an older Node.js version broke my CLI tool. Adding a polyfill wasn’t just a workaround; it was the cleanest and safest way to maintain compatibility without upgrading the entire environment.