Skip to content

require(esm): return the namespace when the module is evicted from require.cache during evaluation - #38072

Open
robobun wants to merge 4 commits into
mainfrom
farm/fcada6d3/require-esm-self-evict
Open

robobun wants to merge 4 commits into
mainfrom
farm/fcada6d3/require-esm-self-evict

Conversation

@robobun

@robobun robobun commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • require() of an ES module that removes itself from require.cache while its top level is still running (directly, or through a dependency that clears the cache) throws instead of returning the namespace:
    TypeError: require() failed to evaluate module "/abs/self-evict.mjs". This is an internal consistentency error.
  • Static import and import() of the same file work; only the require() path is affected. Reproduces on the released 1.4.x binaries and on main.
  • Cause, two stale lookups after evaluation:
    • functionEsmLoadSync (src/jsc/bindings/ZigGlobalObject.cpp, the registryEntry(key) after the loadModuleSync() switch) re-reads the registry to find the record it just evaluated. The eviction removed the entry during evaluation, so it falls into the internal-error branch.
    • overridableRequire and requireESMFromHijackedExtension (src/js/builtins/CommonJS.ts) discard what $requireESM returns and call $esmNamespaceForCjs(id) again. With the entry gone that is undefined, and require() would return the empty placeholder module.exports ({}); that is what the tests get when only the C++ half is applied.
  • Reported by @alii while reviewing bun:test: implement jest.resetModules() and vi.resetModules() #35453 (jest.resetModules() called at the top level of a module that is itself being require()d hits the same path), to be handled separately from that PR.

Fix

  • functionEsmLoadSync: read the record off the registry entry before calling loadModuleSync() and use that record afterwards (Pending/SCC check, evaluationError() check, namespace). The post-load registry lookup is kept only for the case where no entry existed before the call (builtin ES modules, module mocks), because there the load itself creates the entry.
  • overridableRequire / requireESMFromHijackedExtension: use the namespace $requireESM returns. $requireESM returns a namespace or throws, so the namespace !== undefined fallthrough (which returned {}) is removed.
  • Why the held record is the right one: require() only reaches functionEsmLoadSync after fetchCommonJSModule() parsed the file into the registry, and loadModuleSync() links and evaluates the record stored in that entry. An entry's record is written in only two places in JSC (ModuleRegistryEntry::fetchComplete, which runs once when the entry goes Fetching -> Fetched, and moduleLoadStep's setRecord, which stores the value the same entry's module promise resolved with, i.e. that same record), so an entry that already has a record never switches to a different one, and the record read before the load is the instance whose top level ran. Its namespace does not depend on the registry either: export resolution, including export *, walks each record's own loaded-modules map.
  • Resulting behavior matches what a static import of the same file already did: the caller gets the instance that was just evaluated, and the eviction applies to the next require(), which evaluates the file again (asserted by the tests: the second require() sees evaluation 2 and a different namespace object). Node also returns the namespace here (its require.cache has no ESM entries, so the delete is a no-op there).
  • Unchanged: the already-evaluated fast path, the TLA/Pending handling (when an entry pre-exists the held record is the one the registry had), and the require.cache proxy, which still answers from the registry via $esmNamespaceForCjs.
  • Both messages now spell "consistency" correctly.
  • Verified with bun bd test test/js/bun/resolve/require.test.ts: 3 new tests (module deletes itself; a dependency clears the whole cache while the root is mid-load, with an export * resolved after the eviction; the require.extensions wrapper path). All 3 fail on the released binary with the TypeError above, fail with {} exports when only the C++ change is applied, and fail with the TypeError when only the JS change is applied.
  • Also ran: the rest of test/js/bun/resolve (the require-esm-*, esModule* files included), test/regression/issue/24387.test.ts, test/js/node/module/require-extensions.test.ts, node-module-module.test.js, test/js/bun/test/mock/mock-module*.test.ts, test/cli/run/require-cache.test.ts. The only failures were timeouts of the RSS leak loops and the 2000x import() loops under the debug+ASAN build, on fixtures that never enter the require(esm) path, plus two enableCompileCache ownership tests that also fail on the released binary when run as root.

Background

  • ESM registry: JSC's JSModuleLoader maps a resolved path to a ModuleRegistryEntry, which holds the module record (the parsed module: its bindings, environment, and the records it imports). delete require.cache[key] for an ESM key calls $esmRegistryDelete, which removes the entry. The record itself stays alive while something references it, here the C++ local and then the namespace object handed back to the caller.
  • require(esm) flow: overridableRequire (CommonJS.ts) calls the native $require; fetchCommonJSModule transpiles the file, sees it is ESM, stores the source in the registry (which parses it into a record) and returns -1; overridableRequire then calls $requireESM, which tries $esmNamespaceForCjs (registry lookup, undefined unless already evaluated) and otherwise functionEsmLoadSync, whose loadModuleSync() (a Bun addition to JSC that drains the loader's promise reactions synchronously) links and evaluates the graph. The module's top level runs inside that call, which is when the eviction happens.
  • Module namespace object: built from a record by getModuleNamespace(). Resolving its exports, including export * re-exports, uses the loaded-modules map each record filled in while its graph was loading, not the registry.
  • requireESMFromHijackedExtension: the variant used when user code installs a function in require.extensions that calls the builtin loader (Module._extensions['.js']); it assigns the namespace to module.exports itself, so it had the same second lookup.

… require.cache during evaluation

require() of an ES module whose top level (or a dependency's) removes the
module from require.cache while it is still evaluating threw
"require() failed to evaluate module ... internal consistentency error".

functionEsmLoadSync re-read the registry entry after loadModuleSync()
returned to find the record, and overridableRequire /
requireESMFromHijackedExtension then looked the namespace up again with
$esmNamespaceForCjs instead of using what $requireESM returned. Both
lookups fail once the entry is gone.

Hold on to the record that was in the registry when the load started
(fetchCommonJSModule parses the file into the registry before require()
gets here) and use the namespace $requireESM returns. The eviction then
only affects the next require(), which evaluates the file again, the
same as it already behaved for a static import.
@coderabbitai

coderabbitai Bot commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: cf7fd9a4-1b1f-4536-91f6-75f1126d1be1

📥 Commits

Reviewing files that changed from the base of the PR and between b7a0431 and ecb2637.

📒 Files selected for processing (8)
  • src/js/builtins.d.ts
  • src/js/builtins/BunBuiltinNames.h
  • src/js/builtins/CommonJS.ts
  • src/jsc/bindings/JSCommonJSExtensions.cpp
  • src/jsc/bindings/ZigGlobalObject.cpp
  • src/jsc/bindings/ZigGlobalObject.h
  • src/jsc/modules/NodeModuleModule.cpp
  • test/js/bun/resolve/require.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 13, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:15 PM PT - Aug 13th, 2026

✅ @robobun, your commit ecb26372b07de4792dd2e0520afde1c534273c48 passed in Build #94807! 🎉


🧪   To try this PR locally:

bunx bun-pr 38072

That installs a local version of the PR into your bun-38072 executable, so you can run:

bun-38072 --bun

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Status: reproduced on the released binary and on main with a CommonJS file doing require("./self-evict.mjs") where the .mjs runs delete require.cache[import.meta.path] at top level (also with a dependency that clears every require.cache key): TypeError: require() failed to evaluate module ... This is an internal consistentency error.

Fix is in this PR. The three new cases in test/js/bun/resolve/require.test.ts fail on the released binary and pass with the fix.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. The fix is well-reasoned and the tests cover the variant matrix (self-evict, dependency clearing the whole cache with an export * resolved after eviction, and the require.extensions path). Because it changes how functionEsmLoadSync holds a module record across user-JS execution and removes the namespace === undefined fallthrough in overridableRequire, a human look at the JSC/module-loader interaction would still be worthwhile.

Checked: the raw AbstractModuleRecord* held across loadModuleSync() is stack-rooted (conservative scan); dynamicDowncast(nullptr) in the Pending arm is safe when no entry existed and the load's own entry was evicted; $requireESM throws on undefined so the removed fallthrough was only reachable in the bug case; the no-pre-existing-entry path (builtins/mocks) keeps its post-load registry read and behaves as before if that entry is also evicted.

Extended reasoning...

Overview

The PR fixes require(esm) throwing an internal-consistency error when the ES module (or a dependency) deletes its own require.cache entry during top-level evaluation. Three files: src/jsc/bindings/ZigGlobalObject.cpp (functionEsmLoadSync now captures the AbstractModuleRecord* from the registry before loadModuleSync() runs user JS, and reuses it afterward instead of re-reading the possibly-evicted registry), src/js/builtins/CommonJS.ts (overridableRequire and requireESMFromHijackedExtension now use the namespace $requireESM returns instead of a second $esmNamespaceForCjs lookup, and the dead namespace === undefined fallthrough is removed), and three new subprocess tests in test/js/bun/resolve/require.test.ts.

Security risks

None identified. This is module-loader control flow; no parsing of untrusted input, no auth/crypto, no new external inputs.

Level of scrutiny

High. functionEsmLoadSync and the CommonJS builtins are on every require(esm) path. The C++ side now holds a raw JSCell pointer across a call that runs arbitrary user JS — I believe this is safe (JSC's conservative stack scanner roots it, and REVIEW.md says never to blame the scanner), and the record is the same one loadModuleSync() evaluates since fetchCommonJSModule() already stored it in the entry. On the JS side, removing the fallthrough is correct because requireESM throws when exports === undefined, so control never returned to $evaluateCommonJSModule except in the bug scenario (yielding {}). But module loading interacts with SCC/TLA/cycle handling in subtle ways, so someone with JSC module-loader context should confirm the captured-record approach doesn't miss a case where loadModuleSync() replaces the record on an existing entry.

Other factors

The PR description is unusually thorough and enumerates the flow, why the held record is authoritative (namespace resolution walks each record's own loaded-modules map, not the registry), and what was left unchanged. Tests assert the eviction applies to the next require (evaluation counter increments, namespaces differ), assert stderr is empty and exit code last, use tempDir/bunRun per harness conventions, and path.join(dir, ...) on a DisposableString matches existing usage in test/js/bun/io/bun-write-leak.test.ts and others. I also checked that dynamicDowncast<CyclicModuleRecord>(record) with record == nullptr (the builtin/mock path when the freshly-created entry is itself evicted) returns null and falls through to the same error as before — no regression there. The "consistentency" typo fix is applied to both messages. Deferring only because this is core loader code, not because of any specific concern.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

On the one open point (whether loadModuleSync() can swap the record on an entry that already has one): an entry's record is written in exactly two places in JSC. ModuleRegistryEntry::fetchComplete runs once, on the Fetching -> Fetched transition (the sync-replay copy of the reaction bails out because the module promise is already settled), and moduleLoadStep's entry->setRecord(module) stores the value that this same entry's module promise resolved with, which is the record fetchComplete stored. Every branch of hostLoadImportedModule for an existing entry (realm loaded-modules hit, existing load promise, or first load through the module promise) feeds that record into link/evaluate, and moduleLoadStep works on the entry cell held by its context rather than a registry lookup, so eviction during the load does not change which record is evaluated. An entry that has a record when functionEsmLoadSync starts therefore still has the same record afterwards, and that is the one held here. Added this to the PR description as well.

…d; share the namespace shaping

Hold the ModuleRegistryEntry rather than its record: an entry that an
import() graph is still fetching has no record when require() starts,
and the record is created on that same cell during the load. Look the
entry up in the (key, JavaScript) bucket that loadModuleSync() loads;
registryEntry() also matches the JSON/HostDefined entry registered by an
import with a type attribute, whose record this load does not evaluate.

Fold the __esModule / "module.exports" shaping that overridableRequire
and the Module._extensions path each had a copy of into one builtin,
requireESMIntoModule, and drop the unreachable undefined check in
requireESM.
@robobun
robobun requested a review from alii as a code owner August 13, 2026 18:01
Comment thread src/js/builtins/CommonJS.ts Outdated
Comment thread src/js/builtins/CommonJS.ts Outdated
Comment thread src/js/builtins/CommonJS.ts Outdated
Comment thread src/js/builtins/CommonJS.ts
Comment thread src/jsc/bindings/ZigGlobalObject.cpp Outdated
Comment thread src/jsc/bindings/ZigGlobalObject.cpp Outdated
Comment thread src/jsc/bindings/ZigGlobalObject.cpp Outdated
Comment thread src/jsc/bindings/ZigGlobalObject.cpp
Comment thread src/jsc/bindings/ZigGlobalObject.cpp
Comment thread src/jsc/bindings/ZigGlobalObject.cpp Outdated
Comment thread src/jsc/bindings/ZigGlobalObject.cpp Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed this PR and didn't find any bugs. Because it changes the core require(esm) load path in functionEsmLoadSync (holding a raw ModuleRegistryEntry* across loadModuleSync(), which runs arbitrary user JS) and reshapes the builtin-JS side of the same flow, a maintainer familiar with the JSC module loader should confirm the record-immutability invariant and GC-safety reasoning.

What was reviewed:

  • Traced that functionEsmLoadSync now always returns a namespace or throws, so the removed undefined fallthrough in overridableRequire/requireESM was dead outside the eviction case.
  • Checked the registryEntry (any type, for entryExistedBefore) vs javaScriptRegistryEntry split; the new attribute-typed test pins the right entry is used.
  • Verified the rename is complete across JSCommonJSExtensions.cpp, ZigGlobalObject.{h,cpp}, NodeModuleModule.cpp, BunBuiltinNames.h, and the lazy-getter registration.
Extended reasoning...

Overview

This PR fixes require() of an ES module that removes itself from require.cache while its top level is still running. It touches functionEsmLoadSync in ZigGlobalObject.cpp (holds the JavaScript-typed registry entry across loadModuleSync() instead of re-looking it up afterwards), consolidates the duplicated __esModule-shaping logic in CommonJS.ts into a single requireESMIntoModule builtin (called from both overridableRequire and the C++ Module._extensions loader), renames requireESMFromHijackedExtension accordingly across four files, adds a new private builtin name with a lazy getter, and adds five new subprocess tests in require.test.ts.

Security risks

None identified. This is module-loader control flow; no untrusted-input parsing, auth, or crypto.

Level of scrutiny

High. functionEsmLoadSync is on the hot path for every require() of an ES module, and ZigGlobalObject.cpp is one of the most critical files in the codebase. The correctness of the fix rests on a JSC-internal invariant (an entry's record is written only in fetchComplete and moduleLoadStep, never swapped once set) and on the conservative stack scanner keeping the held ModuleRegistryEntry*/AbstractModuleRecord* alive after the entry is unlinked from the registry mid-evaluation. The PR description argues both points carefully and the tests exercise the eviction, but a maintainer who knows the loader should confirm.

Other factors

The bug-hunt pass found nothing. The comment-cop bot's long-comment flags were addressed in follow-up commits and all threads are resolved. Tests use tempDir+bunRun per harness conventions, assert exact JSON output, cover both entry points (overridableRequire and the require.extensions wrapper), and include edge cases (dependency clears the whole cache with an export * resolved after eviction; entry still Fetching from a concurrent import(); attribute-typed sibling entry). The dedup of the two builtin-JS namespace-shaping blocks is a net simplification. No behavior change to the already-evaluated fast path or the TLA/Pending handling.

Jarred-Sumner pushed a commit that referenced this pull request Sep 12, 2026
…#40123)

### Problem
- `delete require.cache[path]` of an ES module that is still loading
evicts its registry entry. The next `import()` of the path builds a
second record for the same module while the loader's `[[LoadedModules]]`
cache and pending microtasks hold the first one. Release: `Segmentation
fault at address 0x10` in `JSModuleLoader::loadModule`. Debug:
`ASSERT(loadedEntry->record() == loaded)` at `JSModuleLoader.cpp:630`
(`import()`), `ASSERT(iter->value.m_module.get() == *resultRecord)` at
`:955` (`require()`). When the records line up, the module evaluates
twice.
- Cause: `functionEsmRegistryDelete`
(`src/jsc/bindings/ZigGlobalObject.cpp`) calls `removeEntry()` for every
state. The `has` and `get` traps already answer "not in the cache" for a
module that has not finished evaluating, so the delete acts on a key the
cache says is absent.
- Shape: a CommonJS dependency of an ES module runs the decache idiom
(`delete require.cache[importer]; import(importer)`) while the importer
is mid-load.

### Fix
- `functionEsmRegistryDelete` evicts only entries whose load has
settled: the record is `Evaluated` (with or without an error) or the
entry caches a fetch, instantiation, or evaluation error. A loading
module keeps its entry, so a later `import()` or `require()` joins the
in-flight load. Node evaluates the module once in all of these cases.
- Deleting an evaluated module still evicts it. The decache idiom and
the leak tests are unchanged.
- Verified: `test/cli/run/require-cache.test.ts`, 5 new tests. Four fail
on the released binary (one segfault, three double evaluations). Also
`test/js/node/module/`, `test/js/bun/test/mock/`,
`test/js/bun/resolve/require*`, `test/js/bun/plugin/`,
`test/cli/hot/hot.test.ts`.

### Background
- The registry maps a module key to a `ModuleRegistryEntry`: the load
promises, the status (`New`, `Fetching`, `Fetched`, or a failure), and
once fetched the record. Bun exposes evaluated ES modules through
`require.cache` so `delete` can evict them. `removeEntry()` is a Bun
addition to JSC.
- A module's CommonJS dependencies run while the module's graph is still
being fetched (the CJS body runs when the synthetic module record is
made), before the module itself evaluates. That is the window these
repros hit.
- `[[LoadedModules]]` is the per-realm and per-module cache from a
specifier to the record it loaded. It is only valid while it agrees with
the registry.

<details><summary>Notes</summary>

Direction: #39674 (WebKit bump for oven-sh/WebKit#472) fixes the same
crash from the engine side and keeps the opposite semantics: deleting an
in-flight module evicts it and the next `import()` loads the file again.
Its `mock.module()` and `Bun.plugin` coverage is still needed with this
change (those paths call `removeEntry()` directly and are not gated
here). Two of its `require-cache.test.ts` tests assert the evict
semantics (`loads: [1, 2]`, and a second `import()` that starts a
replacement load while the first is held) and would need to change if
this lands. #38072's tests assert that a module which deletes itself
during its own evaluation re-evaluates on the second `require()`; with
this change the second `require()` returns the same namespace.

Repro (3 files, release 1.4.0):

```js
// entry.mjs
import("./a.mjs").then(ns => console.log("entry", ns.x));
// a.mjs
import "./b.cjs";
export const x = 1;
console.log("a evaluated");
// b.cjs
delete require.cache[__dirname + "/a.mjs"]; // `in require.cache` is false here
import("./a.mjs").then(ns => console.log("b", ns.x));
```

Before: `a evaluated`, `entry 1`, then the segfault. `bun a.mjs`: `a
evaluated` twice. With `require("./a.mjs")` in b.cjs: `a evaluated`
twice (debug: the `:955` assert). Deleting from a CJS module
`require()`d by the module's own top level (record `Evaluating`): `a
evaluated` twice. After: one evaluation and both imports resolve to the
same namespace in all four shapes. Node prints `a evaluated` once for
each.

Under the debug ASAN build the RSS leak tests in this file time out with
and without this change (they did before it too).

</details>

<!-- robobun:evidence:begin -->

---

**no test proof** · iteration 1 · platform-specific test(s) that do not
run on this machine, deferring to CI, which covers all platforms:
test/cli/run/require-cache.test.ts

<!-- robobun:evidence:end -->
@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

#40123 merged (bc4bea9). delete require.cache[path] of an ES module that has not finished evaluating is now a no-op, so a module that deletes itself (or whose dependency clears the cache) during its own require() keeps its registry entry.

Measured on main at 1d487c4 (debug build) with this PR's four tests and no other change from this PR:

  • The TypeError: require() failed to evaluate module "...". This is an internal consistentency error. no longer occurs in any of them. The first require() returns the namespace (x: 1, evaluation: 1, leaf, cleared, __esModule: true all as expected). The same three fixtures still throw that TypeError on the canary from before the merge (6a92015).
  • returns the namespace when the entry was still being fetched by an import() graph passes.
  • The other three fail only on the lines that expect the eviction to take effect: cachedAfterFirst is true, the second require() returns the same namespace with evaluation: 1, and wrapperCalls is 1. That is the merged behavior (one evaluation), the opposite of what these tests assert.

So the crash this PR was opened for is fixed on main. What is left here is the $requireESMIntoModule cleanup and the held-record change in functionEsmLoadSync, which would need their tests rewritten for the one-evaluation outcome.

@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator Author

Follow-up to the comment above. #40123 does not make this PR unnecessary. The same TypeError still occurs on main through two other eviction paths. Measured on a debug build of main at a749e0a.

Already on main

Still throws on main

functionEsmLoadSync still reads the registry again after loadModuleSync(). Two callers remove the entry with removeEntry() directly, so the check from #40123 does not apply to them.

  1. Bun.plugin build.module() (src/jsc/bindings/BunPlugin.cpp:162), called from the top level of the module that require() is loading:
// self-virtual.mjs
Bun.plugin({
  name: "virtual",
  setup(build) {
    build.module(import.meta.path, () => ({ exports: { x: 2 }, loader: "object" }));
  },
});
export const x = 1;

// entry.cjs
console.log(require("./self-virtual.mjs").x);

bun entry.cjs prints TypeError: require() failed to evaluate module "/abs/self-virtual.mjs". This is an internal consistentency error. import("./self-virtual.mjs") of the same file prints 1. The result is the same when a CommonJS dependency of the module makes the build.module() call.

  1. mock.module() (src/jsc/bindings/BunPlugin.cpp:699), called while the target is fetched but not linked:
// root.mjs
import "./dep.cjs";
export const x = 1;

// dep.cjs
require("bun:test").mock.module(require.resolve("./root.mjs"), () => ({ x: 2 }));

// repro.test.cjs
const { test, expect } = require("bun:test");
test("require() of a module that a dependency mocks mid-load", () => {
  expect(require("./root.mjs").x).toBe(1);
});

bun test ./repro.test.cjs fails with the same TypeError. A second require("./root.mjs") after the TypeError then reaches the loadedEntry->record() == loaded assertion that #39674 covers. mock.module(import.meta.path, ...) from the module's own top level does not hit any of this. The record is Evaluating there, so mock.module() patches the namespace and keeps the entry.

The fix here still applies

With only the functionEsmLoadSync hunk of this PR ported onto main (hold the entry across loadModuleSync()), all three shapes return the namespace from the first require() (x: 1). That matches import() of the same file. For build.module(), the second require() returns the virtual module (x: 2).

Suggested next step

Keep this PR open. Rebase it and reduce it to the functionEsmLoadSync hunk. Replace the tests with the build.module() and mock.module() shapes above. If the delete require.cache tests stay, they need the outcome from #40123: cachedAfterFirst: true, the same namespace, evaluation: 1, wrapperCalls: 1. The $requireESMIntoModule refactor is not needed for the fix.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants