Skip to content

feat(core)!: apply rs.mock to non-literal dynamic import()#1485

Draft
fi3ework wants to merge 5 commits into
mainfrom
fix/mock-nonliteral-dynamic-import
Draft

feat(core)!: apply rs.mock to non-literal dynamic import()#1485
fi3ework wants to merge 5 commits into
mainfrom
fix/mock-nonliteral-dynamic-import

Conversation

@fi3ework

@fi3ework fi3ework commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

A dynamic import() whose specifier is not a static string literal (e.g. a variable) is routed to the worker's native import hook and loaded by Node outside the webpack runtime, so rs.mock() was bypassed (#1454).

  • Direct case — route a non-literal import(request) whose request names a mocked module to the bundle's mock instance via a shared-global resolver (all Node), fixing const s = 'node:os'; import(s).
  • Inner-import case (the reported repro) — install in-thread module.registerHooks (Node >= 22.15 / >= 23.5, feature-detected with silent fallback, installed lazily on the first mock) so a natively-loaded module's inner import of a mocked module is redirected to a worker-realm mock registry; this also covers externalized deps that import a mocked module.

BREAKING: requires @rspack/core >= 2.1.3

Per the release plan (rspack ships first, no old-rspack compatibility): every generated rstest_mock/rstest_unmock call must carry the build-resolved identity {o, r}, emitted unconditionally by rspack since web-infra-dev/rspack#14665. On an older rspack, rs.mock() fails loudly at runtime with an upgrade hint instead of silently missing.

On top of the hard contract, non-literal import(variable) matching is resolved-first:

  • relative mock requests are keyed only by their build-resolved absolute target (a raw ./foo is ambiguous across directories); on a raw-spelling miss the worker resolves a relative runtime specifier against the importing module's directory (the rspack-injected origin) and retries by absolute path — relative, absolute, and file:// spellings all match, two import('./foo') calls from different directories are matched independently, and a relative rs.mock('./dep') declared in a shared helper resolves against the helper's own directory (info.o);
  • alias / tsconfig-paths spellings are matched verbatim only, never expanded at runtime; an unmocked one fails loudly (documented limitation).

Limitations are documented in the mock guide: rspack version floor, Node version floor, compiled (TS/JSX) native targets, function-factory instance divergence, alias spellings, and isolate: false ESM cache.

Merge gate

The last commit pins the rspack canary (2.1.3-canary-7caa6eff) so CI runs against the emitting rspack. Keep this PR draft until a stable @rspack/core release ships the emission and @rsbuild/core picks it up; then drop the pin commit and bump the dependency.

Related Links

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 29, 2026

Copy link
Copy Markdown

Deploying rstest with  Cloudflare Pages  Cloudflare Pages

Latest commit: c94dd6f
Status: ✅  Deploy successful!
Preview URL: https://b5d55947.rstest.pages.dev
Branch Preview URL: https://fix-mock-nonliteral-dynamic.rstest.pages.dev

View logs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3af4f1714a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/mockRuntimeCode.js
Comment thread packages/core/src/core/plugins/mockRuntimeCode.js Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 3af4f17 to 6da09a5 Compare June 29, 2026 11:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6da09a5bda

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/nativeMockHooks.ts Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 6da09a5 to 9cd0343 Compare June 29, 2026 12:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9cd0343d18

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/mockRuntimeCode.js Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 9cd0343 to 232c665 Compare June 29, 2026 12:15

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 232c6654c7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/resolveDynamicImport.ts Outdated
Comment thread packages/core/src/runtime/api/utilities.ts Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 232c665 to 2da800c Compare July 1, 2026 03:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2da800cb2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/loadModule.ts Outdated
Comment thread packages/core/src/core/plugins/mockRuntimeCode.js Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 2da800c to 12df18a Compare July 1, 2026 05:09

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12df18a9f4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/mockRuntimeCode.js Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 12df18a to 99131b0 Compare July 1, 2026 05:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99131b0f20

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/mockRuntimeCode.js Outdated
Comment thread packages/core/src/runtime/worker/mockRegistry.ts Outdated
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 99131b0 to e157937 Compare July 1, 2026 05:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e157937dfe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/core/plugins/mockRuntimeCode.js
Comment thread packages/core/src/runtime/worker/mockRegistry.ts
Comment thread packages/core/src/runtime/worker/nativeMockHooks.ts
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from e157937 to f3ce8c1 Compare July 1, 2026 06:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3ce8c1a05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/nativeMockHooks.ts Outdated
Comment thread packages/core/src/runtime/worker/mockRegistry.ts
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from f3ce8c1 to 9f09037 Compare July 1, 2026 06:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9f090372e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/nativeMockHooks.ts
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from 9f09037 to cb04e34 Compare July 1, 2026 06:43
@fi3ework fi3ework marked this pull request as draft July 1, 2026 08:02
fi3ework added 3 commits July 2, 2026 17:01
A dynamic `import()` with a non-literal specifier is routed to the worker's
native import hook and loaded by Node outside the webpack runtime, so
`rs.mock` was bypassed (#1454).

- Route a non-literal `import(request)` whose request names a mocked module
  to the bundle's mock instance via a shared-global resolver (all Node),
  fixing `const s = 'node:os'; import(s)`. A builtin's two spellings (`os` /
  `node:os`) resolve to one mock, and an unmock with either spelling clears
  both.
- Install in-thread `module.registerHooks` (Node >= 22.15 / >= 23.5,
  feature-detected with silent fallback) so a natively-loaded module's inner
  import of a mocked module is redirected to a worker-realm mock registry —
  fixing the reported repro (a local module reached via a variable `import()`)
  and externalized deps that import a mocked module. The synchronous hooks also
  fire for `require()`; those resolutions are left to Node so a natively-loaded
  CommonJS module gets the real module instead of the ESM synthetic.
- Publish each mock's exports to the native registry as a LAZY producer: it is
  evaluated once, only when a natively-loaded module actually imports the mock,
  so an `rs.mock`/`doMock` factory with side effects keeps its lazy semantics
  and never runs at registration. The synthetic native module mirrors the
  bundle's CJS interop: object and callable (default-function) results are
  served, a whole-object default is synthesized only for a non-`__esModule`
  mock, and an async result is detected by its `Promise` tag (so a `then`
  export is preserved and an async factory instead falls through to the real
  module). A throwing factory propagates its error so a broken mock surfaces on
  the native path, matching the bundled path.
- Skip the mock router for `importActual` (its internal `with: { rstest }`
  attribute), so a non-literal `importActual` of a mocked module returns the
  real module.
- Strip the Node-only native-mock bridge from the browser runtime bundle via
  the RSTEST_TARGET_BROWSER build flag, so its import.meta.resolve /
  pathToFileURL use never reaches browser mode.

Limitations documented in the mock guide: Node version floor, TS/JSX native
targets, function-factory instance divergence, async factories falling through
to the real module, native named-export identity, `mockRequire` being CJS-only,
`require()` on the native path staying unmocked, isolate:false ESM cache, the
shared non-literal resolver under isolate:false with multiple projects, and
relative specifiers resolved against the test file.
Zoom-out cleanup of the native-mock runtime after the #1454 review.

- Remove the `request === undefined` guards in publishNativeMock,
  unpublishNativeMock, rstest_unmock, and registerRequestAlias. They guarded a
  pre-request-literal @rspack/core; @rsbuild/core is pinned (~2.1.1), so the
  RstestPlugin always emits the request literal — the whole non-literal
  `import()` mock resolution depends on it — making the branches dead. `request`
  is now a guaranteed string.
- Align the bundle's `isPromise` with the worker registry's realm-safe
  `[object Promise]` tag check (a cross-realm Promise fails `instanceof`), so
  both realms apply one async-detection predicate.

No behavior change: the full e2e mock suite stays green.
Consume the {o, r} identity newer rspack appends to generated
rstest_mock/rstest_unmock calls (emitMockResolvedInfo), feature-detected
by argument presence so older rspack keeps the previous behavior:

- mockRegistry entries become alias groups: every successful key
  derivation (build-resolved target, declaring-module resolution,
  test-file resolution) registers the same entry, and removal from any
  spelling clears the whole group.
- key derivation lives next to the resolve hook that consumes it
  (nativeMockHooks), injected through the registry like the hook
  installer; the browser build injects no resolver, so registrations
  no-op there and the RSTEST_TARGET_BROWSER define machinery is gone.
- a derivingKey guard keeps the derivation's import.meta.resolve from
  being captured by our own resolve hook (a re-mock of an already
  mocked module would otherwise be keyed by its virtual URL).
- the bundle runtime registers every request spelling (raw, alt builtin
  spelling, build-resolved target) at the one write point, so a literal
  import('os') of a node:os mock now hits and every reader is a single
  lookup.
- pin the non-string-literal import() rewrite callee via
  injectDynamicImportOrigin.functionName instead of piggybacking on
  output.importFunctionName.
@fi3ework fi3ework force-pushed the fix/mock-nonliteral-dynamic-import branch from a70571d to c94dd6f Compare July 2, 2026 09:31
@fi3ework

fi3ework commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c94dd6fe76

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/core/src/runtime/worker/resolveDynamicImport.ts Outdated
};

installFactory(finalModFactory);
publishNativeMock(request, modFactory, info);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Do not publish mockRequire to ESM native hooks

getMockImplementation is shared by rs.mock and rs.mockRequire, but these unconditional publishNativeMock calls also register mockRequire factories with the native ESM module.registerHooks path. When a natively loaded ESM module imports the same specifier, it will now receive the CJS mockRequire mock even though mockRequire is supposed to affect only require()/CJS entries. Guard native publication for !isMockRequire (including the other publish branches).

Useful? React with 👍 / 👎.

BREAKING CHANGE: module mocking now requires @rspack/core >= 2.1.3.

Every generated rstest_mock/rstest_unmock call must carry the
build-resolved identity {o, r} (emitted unconditionally by rspack since
web-infra-dev/rspack#14665); an older rspack now fails loudly at runtime
with an upgrade hint instead of silently missing mock aliases.

On top of the hard contract, non-literal dynamic import() matching
becomes resolved-first:

- relative mock requests are no longer keyed by their raw spelling
  (ambiguous across directories) — only by their build-resolved
  absolute target;
- on a raw-spelling miss, the worker resolves a relative runtime
  specifier against the importing module's directory (the
  rspack-injected origin) and the lookup retries by that absolute path,
  so relative, absolute, and file:// spellings all meet the absolute
  keys; two import('./foo') calls from different directories are
  matched independently, and a relative rs.mock('./dep') declared in a
  shared helper resolves against the helper's own directory (info.o) —
  the testPath-based fallback derivation is gone;
- alias / tsconfig-paths spellings are matched verbatim only, never
  expanded at runtime (an unmocked one fails loudly — Node cannot
  natively load such a specifier either).
Temporary override so CI runs against the canary that emits the
build-resolved mock identity (web-infra-dev/rspack#14665). Drop this
commit once a stable @rspack/core release ships the emission and
@rsbuild/core picks it up.
@fi3ework fi3ework changed the title fix(core): apply rs.mock to non-literal dynamic import() feat(core)!: apply rs.mock to non-literal dynamic import() Jul 3, 2026
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.

[Bug]: rs.mock is bypassed for modules loaded via dynamic import() with a non-literal specifier

1 participant