Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed-iterator-polyfill-followups.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed the Node 20 `Iterator` polyfill so vitest passes again on the prow image: the polyfill is now mirrored into the vm contexts jsdom 30 creates per window, and `NODE_OPTIONS` is rewritten to the polyfill's absolute path so child `node` processes spawned by script tests from tmpdir cwds no longer die with MODULE_NOT_FOUND.
45 changes: 45 additions & 0 deletions scripts/node-iterator-polyfill.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,49 @@
// prototype methods; on Node >= 22 the native implementation is kept.
if (typeof globalThis.Iterator === 'undefined') {
require('core-js/actual/iterator')

// jsdom >= 30 also reads every JS global out of the fresh vm context it
// creates per window (lib/jsdom/browser/Window.js: vm.runInContext(name,
// window) for each host global it found, which now includes our polyfilled
// `Iterator`). A fresh context on Node 20 has no native Iterator, so window
// creation throws "ReferenceError: Iterator is not defined" from inside
// runInContext. Mirror the polyfilled Iterator into every new context.
const vm = require('vm')
const originalCreateContext = vm.createContext
vm.createContext = function (...args) {
const context = originalCreateContext.apply(this, args)
// createContext returns the contextified object (or the context's global
// object with DONT_CONTEXTIFY); property sets on it reflect as globals
// inside the context.
const globalObject =
context !== null && typeof context === 'object' ? context : args[0]
try {
if (
globalObject &&
vm.runInContext('typeof Iterator', globalObject) === 'undefined'
) {
globalObject.Iterator = globalThis.Iterator
}
} catch {
// Non-contextified or frozen targets: leave untouched.
}
return context
}
}

// The `test` script preloads this file with a cwd-relative path
// (`--require ./scripts/node-iterator-polyfill.cjs`). Node resolves that
// against the cwd of EACH process, so a child `node` process a test spawns
// with a tmpdir cwd dies at startup with MODULE_NOT_FOUND before running
// anything. Rewrite NODE_OPTIONS to this file's absolute path so inherited
// environments keep working from any cwd.
const RELATIVE_SPEC = './scripts/node-iterator-polyfill.cjs'
if (
process.env.NODE_OPTIONS &&
process.env.NODE_OPTIONS.includes(RELATIVE_SPEC) &&
!__filename.includes(' ')
) {
process.env.NODE_OPTIONS = process.env.NODE_OPTIONS
.split(RELATIVE_SPEC)
.join(__filename)
}
Loading