Skip to content

feat: add polyfills build option to enable or disable polyfills - #496

Merged
dsherret merged 1 commit into
denoland:mainfrom
dsherret:feat/polyfill-options
Jul 26, 2026
Merged

feat: add polyfills build option to enable or disable polyfills#496
dsherret merged 1 commit into
denoland:mainfrom
dsherret:feat/polyfill-options

Conversation

@dsherret

Copy link
Copy Markdown
Collaborator

Closes #471

Polyfills were previously selected entirely by compilerOptions.target: Latest disabled all of them and every other target enabled whichever ones matched. There was no way to keep a target and opt out of an individual polyfill.

This adds a polyfills build option that overrides the target per polyfill:

await build({
  // ...etc...
  polyfills: {
    importMeta: false,
  },
});

A bare true/false enables or disables all of them. Anything left unspecified still resolves from the target, so polyfills overrides the target rather than replacing it.

Supported names: arrayFindLast, arrayFromAsync, errorCause, importMeta, objectHasOwn, promiseWithResolvers, stringReplaceAll.

The import.meta bug

This also fixes the runtime error reported in #471, which turned out to be a separate pre-existing bug.

The import.meta call sites are rewritten by a TypeScript compiler transform in lib/compiler_transforms.ts, not by the Rust transform, and that rewrite was never gated on the target. Setting target: "Latest" dropped the polyfill file (via the Latest early-return in polyfills_for_target) while still emitting globalThis[Symbol.for("import-meta-ponyfill-esmodule")](import.meta) calls into it — so the output threw at runtime. The rewrite is now gated on the same resolved decision as the polyfill file.

Disabling the importMeta polyfill requires scriptModule: false, since import.meta is a syntax error in CommonJS. It errors with an explanation rather than emitting invalid output.

Implementation

  • Polyfill gains fn name(&self) -> &'static str, and polyfills_for_target takes an overrides map. The Latest early-return moves into the filter so an explicit override can turn a polyfill back on at Latest; defaults are unchanged.
  • polyfills is threaded through TransformOptions in rs-lib, wasm, and transform.ts.
  • lib/polyfills.ts holds the option resolution and the import.meta decision, and errors on unknown polyfill names for JS callers (TS callers get it from the PolyfillName union).

Note on types

The polyfill scripts carry the ambient declare global declarations for the features they polyfill, so opting out of one means relying on compilerOptions.lib for those declarations instead. This is documented in the README. It's worth being aware of because users will hit a type error before a runtime one — e.g. polyfills: false on a project using Object.hasOwn needs lib: ["ESNext"], and import.meta.main is a Deno-ism that exists in no TS lib, so it stops type checking entirely with importMeta: false.

Tests

  • 3 Rust integration tests: override disables for a target, override enables at Latest, and an override affecting only the named polyfill.
  • lib/polyfills.test.ts unit tests for option resolution and the import.meta decision.
  • 3 integration tests: import.meta left intact for an ESM-only build with the polyfill off, the script-module error, and polyfills: false emitting no polyfill file.

deno test -A (111 passed), cargo test --workspace (83 passed), deno fmt/deno lint/cargo fmt all clean.

Polyfills were previously selected entirely by `compilerOptions.target`,
with `Latest` disabling all of them and every other target enabling
whichever ones matched. The new `polyfills` build option overrides that
per polyfill:

    polyfills: { importMeta: false }

A boolean enables or disables all of them. Anything left unspecified
still resolves from the target.

This also fixes denoland#471. The `import.meta` call sites are rewritten by a
TypeScript compiler transform in `lib/compiler_transforms.ts` rather than
by the Rust transform, and that rewrite was not gated on the target at
all. Setting `target: "Latest"` therefore dropped the polyfill file while
still emitting calls into it, producing output that threw at runtime. The
rewrite is now gated on the same resolved decision as the polyfill file.

Disabling the `importMeta` polyfill requires `scriptModule: false` since
`import.meta` is a syntax error in CommonJS; it errors otherwise instead
of emitting invalid output.
@dsherret
dsherret merged commit 8a0d242 into denoland:main Jul 26, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possibility to disable some polyfills

1 participant