feat: use Node.js timers by default and remove global proxy#33249
Conversation
Reland of #33118 and #33122 (reverted in #33211). - Switches global setTimeout/setInterval/clearTimeout/clearInterval to use Node.js timer APIs instead of Web APIs by default - Timer IDs are now Timeout objects instead of numbers - Removes the v8 named property handler proxy in ext/node/global.rs - Simplifies CJS wrapper to match Node.js Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Fixing the type declarations for timer return typesWith this PR changing Root cause: node types not always loadedDeno provides node globals ( Proposed approach
This way:
|
lunadogbot
left a comment
There was a problem hiding this comment.
Read the timer plumbing in 02_timers.js, the global-scope swap in 98_global_scope_shared.js, the test ignore list in tests/unit/timers_test.ts, and the WPT expectation diffs. The wiring is correct -- setTimeout/setInterval/clearTimeout/clearInterval now point at nodeSet*/nodeClear* from the windowOrWorkerGlobalScope record, the unstable nodeGlobals branch is gone, and the Timeout-vs-number impedance is handled in refTimer/unrefTimer via the new if (typeof id !== 'number') id = id[kTimerId] cast.
Three concerns I'd want explicit decisions on before merging -- not blocking, but each is a user-visible spec/contract change that may surprise downstream code:
1. WHATWG HTML spec regressions are now permanent. tests/unit/timers_test.ts marks 5 cases as Deno.test.ignore with // TODO(bartlomieju): this test is not valid -- these cover string-callback eval (setTimeout("code", 100)), this-binding to globalThis, and primordial scope isolation, all of which the HTML spec mandates. The PR body doesn't note these as casualties; worth at least a line in the PR description and a release note so users don't think string-callback support disappeared as a bug.
2. refTimer(id)/unrefTimer(id) null-safety changed. Old getActiveTimer(null) returned undefined and the if (timer) guard skipped silently. New code does if (typeof id !== 'number') id = id[kTimerId] before the guard, so passing null or undefined now throws TypeError: Cannot read properties of null/undefined (reading 'Symbol(...)' ). Code in the wild that defensively calls unrefTimer(timer.id) where timer may have already been cleared would change from no-op to throw. A if (id == null) return; early-return at the top of both functions would preserve the prior behavior cheaply.
3. setTimeout return type is now Timeout not number. The body says numeric coercion still works via [Symbol.toPrimitive], which is true, but reference-equality patterns silently break: const ids = new Set(); ids.add(setTimeout(fn,1)); ids.has(1) now returns false because the Set stored a Timeout object. Number(id) === id also flips from true to false. Probably acceptable as a Node-compat trade-off, but again worth a release-note callout for users who used to do Map<number, ...> keyed by timer IDs.
Didn't review ext/node/global.rs removal in detail (487 lines deleted, replacing the v8 named-property handler proxy) -- trusting that the test suite passing covers the cases that proxy was load-bearing for. If there are subtle global-access patterns it was intercepting, those would only surface in user code, not CI.
# Conflicts: # ext/node/polyfills/02_init.js
The symbol was removed from stream_wrap.ts in #33297 but the import in 02_init.js survived the merge, breaking the snapshot build.
Node.js `setTimeout`, `setInterval`, `clearTimeout`, and `clearInterval` globals became the default in #33249, so the `node-globals` unstable feature now only re-installs the exact same functions that are already present on the global scope. Enabling it does nothing, which makes the `--unstable-node-globals` flag a no-op. This hides the flag from `--help`, removes the dead runtime block that re-installed the node timers, and stops the Node-compat argument translator from emitting the redundant flag. The feature entry itself is kept so existing callers passing `--unstable-node-globals` (or running with `DENO_COMPAT=1`) continue to parse without erroring.
Summary
Reland of #33118 and #33122 (reverted in #33211).
setTimeout/setInterval/clearTimeout/clearIntervalto use Node.js timer APIs instead of Web APIs by defaultTimeoutobjects instead of numbers (Node.jsTimeouthas[Symbol.toPrimitive]so numeric coercion still works)ext/node/global.rsthat intercepted global accesses based on Node/Deno contextnodeGlobalsbag🤖 Generated with Claude Code