esbuild-plugin-polyfill-node

An esbuild plugin that polyfills Node.js built-ins and globals for edge runtimes and Deno Deploy.

Library
npm
v0.3.0
60stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
26/100Needs Attention
Development Activity0
Maintenance0
Community32
Maturity52
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
51/100Fair
Architecture68
Code Quality45
Innovation55
Learning Curve35

esbuild-plugin-polyfill-node lets esbuild-based bundles run Node.js-flavored code in environments that don’t provide Node’s built-in modules or globals, such as Cloudflare Workers, Vercel Edge Functions, and other edge/serverless runtimes. It intercepts imports of Node built-ins like fs, crypto, stream, and path, and redirects them to browser-safe shims sourced from @jspm/core, while also injecting commonly needed globals such as Buffer, process, global, __dirname, and __filename via esbuild’s inject mechanism.

The package ships two entry points: polyfillNode, a general-purpose plugin suited to most edge environments, and polyfillNodeForDeno, a variant that instead points unresolved Node built-ins at Deno’s std/node compatibility library and is specifically useful for Deno Deploy, which lacks native node: module support. Per-module control lets consumers disable a polyfill entirely, force an empty stub (to avoid pulling in unnecessary code, as is the default for fs and crypto), or opt into the real shim.

What You Get

  • A polyfillNode esbuild plugin that shims Node built-ins (fs, crypto, path, stream, http, and dozens more) using @jspm/core’s browser-targeted implementations
  • A separate polyfillNodeForDeno plugin that redirects the same built-ins to Deno’s std/node compatibility library, aimed at Deno Deploy where native node: specifiers aren’t supported
  • Automatic injection of commonly needed globals (Buffer, process, global, __dirname, __filename, optional navigator) via esbuild’s inject option
  • Per-module override control — enable, disable, or force an empty stub for any individual built-in to control bundle size
  • Sensible size-conscious defaults: fs, crypto, dns, dgram, cluster, repl, and tls are stubbed empty unless explicitly enabled

Common Use Cases

  • Deploying a library or app written against Node APIs to Cloudflare Workers or another edge runtime via esbuild
  • Bundling code for Deno Deploy that still imports Node built-ins like crypto or stream
  • Preventing process.env.NODE_ENV checks in dependencies from pulling in a full process polyfill unnecessarily
  • Shipping a universal (isomorphic) package that needs to build cleanly for both Node and non-Node bundler targets

Under The Hood

Architecture The entire plugin lives in a single module (src/index.ts) exporting two factory functions, polyfillNode and polyfillNodeForDeno, each returning an esbuild Plugin object. Both register an onResolve hook keyed by a regex built from a fixed set of supported module names (jspmPolyiflls / denoPolyfills); when esbuild encounters an import matching a bare or node:-prefixed built-in, the hook redirects it either to a browser-shim file resolved on disk via @jspm/core’s nodelibs package (for polyfillNode) or to a remote https://deno.land/std/node/*.ts URL marked external: true (for polyfillNodeForDeno). Global injections are handled separately by pushing small hand-written shim files from polyfills/ onto esbuild’s initialOptions.inject array. A resolveImport helper lazily loads import-meta-resolve to locate @jspm/core’s filesystem path at runtime. There’s no additional layering — it’s a flat, single-purpose adapter over esbuild’s plugin API, and the resolve-interception logic is the one thing every other behavior depends on.

Tech Stack Written in strict-mode TypeScript, compiled to ES2020/ES6 output and bundled with tsup into both ESM (dist/index.js) and CJS (dist/index.cjs) builds plus generated .d.ts types, targeting Node 14 as the runtime floor. Runtime dependencies are @jspm/core@^2.0.1 (source of the browser-safe Node builtin shims) and import-meta-resolve@^3.0.0; esbuild itself is declared only as a peer dependency (*), so any installed esbuild version can use it. Tooling is pnpm-managed, with @cyco130/eslint-config, ESLint, Prettier, and rimraf rounding out the dev dependencies.

Code Quality There are no test files in the repository — the test npm script runs only tsc --noEmit (typecheck), eslint (lint), and prettier --check (formatting), with no unit or integration tests exercising the plugin’s actual resolve/inject behavior. Error handling is minimal but explicit where it exists, such as throwing when a requested module name falls outside the supported set. TypeScript strict mode is enabled and the public option types (PolyfillNodeOptions, PolyfillNodeForDenoOptions) are fully typed with JSDoc comments on most fields. ESLint and Prettier are configured, but there is no CI workflow in .github/ (only a Renovate config for dependency bumps), so lint/typecheck/format aren’t automatically enforced on push or PR.

API Design The public surface is small and low-friction: a single options object per plugin with defaults tuned to avoid bloating bundles (fs and crypto default to empty stubs unless explicitly enabled), and consistent per-module override semantics (false / true / "empty") shared across both plugins. Getting started requires no boilerplate beyond plugins: [polyfillNode()] in an esbuild config. The two-plugin split (general edge vs. Deno-specific) is a deliberate, well-documented design choice that most comparable polyfill plugins don’t offer, though the project is explicitly seeking new maintainers per the README, which is a notable caveat for adoption.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search