Skip to content

feat(ext/node): implement module.enableCompileCache and companions#34190

Merged
littledivy merged 2 commits into
mainfrom
orch/divybot-123
May 17, 2026
Merged

feat(ext/node): implement module.enableCompileCache and companions#34190
littledivy merged 2 commits into
mainfrom
orch/divybot-123

Conversation

@divybot

@divybot divybot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Polyfill the Node 22.8+ compile-cache surface on node:module: enableCompileCache(dirOrOptions), flushCompileCache(), getCompileCacheDir(), the module.constants.compileCacheStatus enum (ENABLED / ALREADY_ENABLED / FAILED / DISABLED), and the NODE_COMPILE_CACHE / NODE_DISABLE_COMPILE_CACHE / NODE_COMPILE_CACHE_PORTABLE env-var handshake.
  • Hook Module._extensions[".js" | ".cjs" | ".mjs" | ".mts"] so that when NODE_DEBUG_NATIVE=COMPILE_CACHE is set, the polyfill emits the same reading cache from … / was not initialized, initializing the in-memory entry / writing cache for … success / cache for … was accepted, keeping the in-memory entry / skip persisting … because cache was the same traces that the parallel/test-compile-cache-* Node.js compat tests assert on. A small content-hash cache is written under <dir>/<bucket>/<hash> so the hit/miss flow stays correct across runs (with the bad-syntax path rolling back its entry).
  • Forward --require / --import from the node→deno CLI translator (libs/node_shim) so that spawnSync(process.execPath, ['-r', wrapper, main]) — the pattern these tests use — actually preloads the wrapper. Without this the translator was dropping -r and the wrapper never ran.
  • Wire node:module's loadESMFromCJS for .mjs/.mts through a thin trampoline so the same traces fire for require()'d ESM modules.

12 previously-failing tests are added to tests/node_compat/config.jsonc as expected passes; 4 .mjs/dynamic-import-driven tests (test-compile-cache-esm.js, test-compile-cache-portable-esm.js, test-compile-cache-dynamic-import.js, test-compile-cache-bad-syntax.js) are marked ignore with an explanation: their assertions look for the debug trace on modules loaded through Deno's ESM main / dynamic-import paths, which bypass node:module and therefore can't emit the trace from this polyfill.

Closes denoland/orchid#123

Test plan

  • cargo test --test node_compat -- parallel::test-compile-cache — 17 pass, 7 ignore (3 permission-model + 4 ESM-entry), 0 fail.
  • deno lint clean for ext/node/polyfills/01_require.js and ext/node/polyfills/internal/compile_cache.js.
  • cargo clippy -p deno_node -p node_shim clean.
  • CI green.

🤖 Generated with Claude Code

divybot and others added 2 commits May 17, 2026 14:04
Add the Node 22.8+ compile-cache surface (`enableCompileCache`,
`flushCompileCache`, `getCompileCacheDir`, `module.constants.compileCacheStatus`,
and the `NODE_COMPILE_CACHE` / `NODE_DISABLE_COMPILE_CACHE` /
`NODE_COMPILE_CACHE_PORTABLE` env-var handshake) to `node:module`.

The actual V8 code cache is still owned by Deno's runtime, so this layer
focuses on the public API and on the debug traces (`NODE_DEBUG_NATIVE=COMPILE_CACHE`)
that the `parallel/test-compile-cache-*` Node.js compat tests assert on. A
tiny on-disk content-hash cache backs the `writing cache for <file>: success`
/ `skip persisting <file> because cache was the same` alternation so the
hit/miss flow looks correct across runs.

Also forward `--require`/`--import` modules through the node→deno CLI
translator so `spawnSync(process.execPath, ['-r', wrapper, main])` (the
pattern these tests use) actually preloads the wrapper.

Adds 12 previously-failing tests to `tests/node_compat/config.jsonc`.
Four `.mjs`/dynamic-import tests are marked `ignore` with explanations:
their assertions check stderr for entries loaded via Deno's ESM main /
dynamic-import paths, which bypass `node:module` and so can't emit the
trace from this polyfill.

Closes denoland/orchid#123

Co-Authored-By: Divy Srivastava <[email protected]>
…port forwarding

The previous commit started forwarding `--require` / `--import` through the
node->deno CLI translator. These two tests had been silently passing because
the flags were being dropped before they reached deno:

- `parallel/test-preload-self-referential.js` calls `-r self_ref` to preload
  a package by its own bare-specifier name; Deno's `--require` does not run
  this through Node module resolution, so the require now fails to find
  `self_ref`.

- `es-module/test-import-preload-require-cycle.js` calls `--import preload.mjs`
  where the preload uses `module.registerHooks()`, which is not implemented
  yet in Deno (tracked in #34081).

Mark both as `ignore` with the explanation. Both were already flaky in CI.

Co-Authored-By: Divy Srivastava <[email protected]>

@littledivy littledivy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@littledivy
littledivy merged commit 04cbd8c into main May 17, 2026
136 checks passed
@littledivy
littledivy deleted the orch/divybot-123 branch May 17, 2026 15:05
bartlomieju pushed a commit that referenced this pull request May 25, 2026
…ons" (#34190) (#34348)

## Summary

Reverts #34190 (commit 04cbd8c).

Deno already enables V8 code cache by default, so polyfilling Node's
`module.enableCompileCache` / `flushCompileCache` / `getCompileCacheDir`
surface on top doesn't actually buy us any additional caching — it just
shadows the engine-level cache that's already on. What it *does* buy us
is reads of `NODE_COMPILE_CACHE`, `NODE_DISABLE_COMPILE_CACHE`,
`NODE_COMPILE_CACHE_PORTABLE`, and `NODE_DEBUG_NATIVE` at startup, which
now trip `--allow-env` prompts on scripts that previously ran with just
`--allow-write` (reported in #34326).

Net effect of the revert:

- Removes the `node:module` compile-cache API surface and its
`module.constants.compileCacheStatus` enum.
- Removes the `_extensions[".js" | ".cjs" | ".mjs" | ".mts"]` hooks that
emitted the COMPILE_CACHE debug traces and unconditionally read those
env vars.
- Removes `ext/node/polyfills/internal/compile_cache.js` (471 lines).
- Drops the `libs/node_shim` `--require`/`--import` forwarding —
restores the prior (silent-drop) behavior for the node→deno CLI
translator. The two compat tests that came along for the ride
(`es-module/test-import-preload-require-cycle.js`,
`parallel/test-preload-self-referential.js`) revert to the same shape
they had before #34190.
- Drops the corresponding `parallel/test-compile-cache-*` entries from
`tests/node_compat/config.jsonc`; the permission-model-gated tests that
pre-existed are left untouched.

Refs #34326

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…oland#34190)

## Summary

- Polyfill the Node 22.8+ compile-cache surface on `node:module`:
`enableCompileCache(dirOrOptions)`, `flushCompileCache()`,
`getCompileCacheDir()`, the `module.constants.compileCacheStatus` enum
(`ENABLED` / `ALREADY_ENABLED` / `FAILED` / `DISABLED`), and the
`NODE_COMPILE_CACHE` / `NODE_DISABLE_COMPILE_CACHE` /
`NODE_COMPILE_CACHE_PORTABLE` env-var handshake.
- Hook `Module._extensions[".js" | ".cjs" | ".mjs" | ".mts"]` so that
when `NODE_DEBUG_NATIVE=COMPILE_CACHE` is set, the polyfill emits the
same `reading cache from … / was not initialized, initializing the
in-memory entry / writing cache for … success / cache for … was
accepted, keeping the in-memory entry / skip persisting … because cache
was the same` traces that the `parallel/test-compile-cache-*` Node.js
compat tests assert on. A small content-hash cache is written under
`<dir>/<bucket>/<hash>` so the hit/miss flow stays correct across runs
(with the bad-syntax path rolling back its entry).
- Forward `--require` / `--import` from the node→deno CLI translator
(`libs/node_shim`) so that `spawnSync(process.execPath, ['-r', wrapper,
main])` — the pattern these tests use — actually preloads the wrapper.
Without this the translator was dropping `-r` and the wrapper never ran.
- Wire `node:module`'s `loadESMFromCJS` for `.mjs`/`.mts` through a thin
trampoline so the same traces fire for `require()`'d ESM modules.

12 previously-failing tests are added to
`tests/node_compat/config.jsonc` as expected passes; 4
`.mjs`/dynamic-import-driven tests (`test-compile-cache-esm.js`,
`test-compile-cache-portable-esm.js`,
`test-compile-cache-dynamic-import.js`,
`test-compile-cache-bad-syntax.js`) are marked `ignore` with an
explanation: their assertions look for the debug trace on modules loaded
through Deno's ESM main / dynamic-import paths, which bypass
`node:module` and therefore can't emit the trace from this polyfill.

Closes denoland/orchid#123

## Test plan

- [x] `cargo test --test node_compat -- parallel::test-compile-cache` —
17 pass, 7 `ignore` (3 permission-model + 4 ESM-entry), 0 fail.
- [x] `deno lint` clean for `ext/node/polyfills/01_require.js` and
`ext/node/polyfills/internal/compile_cache.js`.
- [x] `cargo clippy -p deno_node -p node_shim` clean.
- [ ] CI green.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
littledivy added a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…ons" (denoland#34190) (denoland#34348)

## Summary

Reverts denoland#34190 (commit 04cbd8c).

Deno already enables V8 code cache by default, so polyfilling Node's
`module.enableCompileCache` / `flushCompileCache` / `getCompileCacheDir`
surface on top doesn't actually buy us any additional caching — it just
shadows the engine-level cache that's already on. What it *does* buy us
is reads of `NODE_COMPILE_CACHE`, `NODE_DISABLE_COMPILE_CACHE`,
`NODE_COMPILE_CACHE_PORTABLE`, and `NODE_DEBUG_NATIVE` at startup, which
now trip `--allow-env` prompts on scripts that previously ran with just
`--allow-write` (reported in denoland#34326).

Net effect of the revert:

- Removes the `node:module` compile-cache API surface and its
`module.constants.compileCacheStatus` enum.
- Removes the `_extensions[".js" | ".cjs" | ".mjs" | ".mts"]` hooks that
emitted the COMPILE_CACHE debug traces and unconditionally read those
env vars.
- Removes `ext/node/polyfills/internal/compile_cache.js` (471 lines).
- Drops the `libs/node_shim` `--require`/`--import` forwarding —
restores the prior (silent-drop) behavior for the node→deno CLI
translator. The two compat tests that came along for the ride
(`es-module/test-import-preload-require-cycle.js`,
`parallel/test-preload-self-referential.js`) revert to the same shape
they had before denoland#34190.
- Drops the corresponding `parallel/test-compile-cache-*` entries from
`tests/node_compat/config.jsonc`; the permission-model-gated tests that
pre-existed are left untouched.

Refs denoland#34326

Co-authored-by: divybot <[email protected]>
Co-authored-by: Divy Srivastava <[email protected]>
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.

2 participants