Skip to content

fix(resolution): leaf-aware subpath imports, real package.json, preset subpath shadowing#68

Merged
danfry1 merged 2 commits into
mainfrom
fix/subpath-resolution-fidelity
Jul 2, 2026
Merged

fix(resolution): leaf-aware subpath imports, real package.json, preset subpath shadowing#68
danfry1 merged 2 commits into
mainfrom
fix/subpath-resolution-fidelity

Conversation

@danfry1

@danfry1 danfry1 commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Problem

Three resolution-fidelity gaps around deep (subpath) imports, all failing silently:

  1. RN subpath default exports (mock engine). import Platform from 'react-native/Libraries/Utilities/Platform' — the dominant deep-import pattern in ecosystem libraries and migrated Jest suites — received the entire mock object as Platform, so Platform.OS was silently undefined. Same on the CJS side.
  2. Package manifests virtualized. require('react-native/package.json').version (a common version gate) read the mock and got undefined, sending version-gated libraries down wrong code paths.
  3. Preset shadowing skipped subpaths (both engines). import Swipeable from 'react-native-gesture-handler/Swipeable' bypassed the preset mock entirely and loaded the package's real native-runtime code — or failed resolution outright on package versions that no longer ship the deep file (RNGH 3.x dropped /Swipeable).

Change

  • Virtual RN subpath modules (ESM) and the CJS bridge derive the intended export from the subpath's basename and serve it as the default. CJS requires get Babel-interop shape ({ __esModule, default } via a live Proxy wrapper) so both require('.../Platform').OS and _interopRequireDefault consumers work. Unknown leaves keep the previous whole-mock fallback.
  • Both interception layers exempt package.json subpaths, resolving the real manifest; the mock fallback is kept when the package is not installed.
  • All three preset-redirect layers (Vite plugin resolveId/load, ESM loader hook, CJS require hook) now match subpaths of preset packages, leaf-aware. Exempt: JSON subpaths, asset-extension subpaths (fonts/images keep stubbing from their real files), and Node-safe utility entries (jest-utils, jestSetup, mock, plugin), which pass through to the real file. Unknown leaves fall back to the root mock and warn under diagnostics.
  • CJS interop wrappers are memoized per specifier for identity stability, keyed by the per-package mock object — not the __vitest_native_preset_mocks container, which the hot runtime reuses across files while rebuilding the per-package values. (An earlier container-keyed draft served file 1's mocks to every later file in a hot worker; caught in pre-merge review via a two-file repro.)

Tests

  • tests/regression.test.ts: leaf default exports (ESM + CJS), interop shape, identity stability, real-manifest reads (ESM + CJS).
  • tests/native-unit.test.ts: loader subpath redirect + JSON/asset passthrough, specifier helpers, plugin-level resolveId/load for preset subpaths, react-native/package.json carve-out, leaf-aware virtual code generation.
  • tests-native/preset-subpath.test.tsx (runs under stock and hot configs): deep ESM import renders via the preset mock, root-vs-subpath identity, utility passthrough (jest-utils), real-manifest read.
  • tests-native/preset-subpath-cross-file.test.tsx: cross-file twin — catches stale subpath caches in a shared hot worker as an identity mismatch. Verified in a single hot worker with --maxWorkers=1 --no-file-parallelism.

Full gate: mock suite 1291 passed, native suites (stock/hot/hot-isolation/android/navigation) green, crosscheck 75/75, packed consumer fixtures green, lint/format/typecheck clean.

danfry1 added 2 commits July 2, 2026 21:55
…t subpath shadowing

Three resolution-fidelity gaps around deep (subpath) imports, all silent
failures:

- react-native subpath default exports (mock engine): import Platform from
  'react-native/Libraries/Utilities/Platform' received the whole mock object,
  so Platform.OS was undefined with no error. Virtual subpath modules and the
  CJS bridge now derive the intended export from the subpath basename and
  serve it as the default; CJS requires get Babel-interop shape via a live
  wrapper so both direct-property and _interopRequireDefault consumers work.
  Unknown leaves keep the whole-mock fallback.

- Package manifests: require('react-native/package.json').version (and preset
  equivalents) read the mock instead of the real file, breaking version
  gates. Both interception layers now exempt manifests, falling back to the
  mock only when the package is not installed.

- Preset subpath shadowing (both engines): deep imports of preset packages
  (react-native-gesture-handler/Swipeable) bypassed the preset mock and
  loaded real native-runtime code, or failed resolution on versions that no
  longer ship the deep file. All three redirect layers (Vite plugin, ESM
  loader hook, CJS require hook) now match subpaths leaf-aware. Exempt:
  JSON, asset extensions, and Node-safe utility entries (jest-utils,
  jestSetup, mock, plugin), which pass through to the real file. Unknown
  leaves warn under diagnostics.

CJS interop wrappers are memoized per specifier keyed by the per-package
mock object — not the container, which the hot runtime reuses across files
while rebuilding the per-package values (a container-keyed memo served
file 1's mocks to every later file in a hot worker; caught in review with a
two-file repro, now guarded by a cross-file identity test).
The end-to-end assertion required RNGH 3.0's real jest-utils module graph to
load under Node — its ESM flavor's extensionless imports fail to resolve on
some environments, which is the package's own Node compatibility, not this
project's contract (and matches the pre-redirect status quo for these paths).
The behavioral guarantee — utility subpaths are never shadowed — is now
asserted at the loader and plugin resolution layers, with both packages
registered as presets so the pass-through is attributable to the
utility-leaf exemption.
@danfry1 danfry1 merged commit 57f155d into main Jul 2, 2026
17 checks passed
@danfry1 danfry1 deleted the fix/subpath-resolution-fidelity branch July 2, 2026 21:13
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.

1 participant