Skip to content

fix(core): reuse single module instance in jiti config loading on Windows#4306

Closed
claude[bot] wants to merge 1 commit into
mainfrom
fix/jiti-windows-module-dedup-3949
Closed

fix(core): reuse single module instance in jiti config loading on Windows#4306
claude[bot] wants to merge 1 commit into
mainfrom
fix/jiti-windows-module-dedup-3949

Conversation

@claude

@claude claude Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard, Niklas Wenzel · Slack thread

Fixes #3949

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project follows, as appropriate.
  • The changes are appropriately documented (if applicable).
  • The changes have sufficient test coverage (if applicable).
  • The testsuite passes successfully on my local machine (if applicable).

Summarize your changes:

Before / after

Before, on Windows, a project with a TypeScript Forge config that uses the Webpack plugin would silently drop index.html from the packaged app (a regression in v7.8.1). After this change, index.html is emitted again.

How

Forge loads configs via jiti. On Windows (a case-insensitive filesystem), jiti's resolved module filenames aren't case-canonicalized against Node's realpath-keyed require.cache, so jiti's internal module-cache dedup misses the copy Node already loaded and evaluates a second copy of a dependency. When that dependency is webpack, the duplicate copy's Compilation.PROCESS_ASSETS_STAGE_* constants come back undefined, so plugins tap processAssets at the wrong stage and the asset (e.g. index.html) never gets emitted.

The fix wraps the createRequire jiti captures at its own load time — win32 only, scoped to exactly the require('jiti') call — so the require jiti builds for itself gets a case-insensitive view over the same module cache, and its dedup reuses the single already-loaded instance. Module.createRequire is restored in a finally.

Properties of the fix:

  • General — helps for any dependency that gets duplicated this way, not just webpack.
  • Keeps async config loading — no change to how configs are imported.
  • Minimal blast radius — only jiti's own require view is affected; the global require.cache is never written or replaced. Off-win32, jiti is loaded completely untouched.

Testing

Added focused unit coverage for the case-insensitive module-cache wrapper in packages/api/core/spec/fast/util/forge-config.spec.ts (case-insensitive get/has resolution to the single loaded object, exact-key fast path, no-match behaviour, index invalidation on add/delete, and write-through to the underlying cache). The existing forge.config.ts/.cts/.mts resolution tests continue to exercise the jiti load path.

Note

This is a Windows-only regression, confirmed via the reporter's diagnostics. The dedup fix is verified on Linux by simulating the casing miss; final Windows confirmation from the reporter is in progress.

…dows

On Windows, Forge loads TypeScript configs through jiti, whose
internally-resolved module filenames are not case-canonicalized against
Node's realpath-keyed require.cache. jiti's module-cache dedup therefore
misses a dependency Node has already loaded and evaluates a second copy.
When that dependency is webpack, the duplicate's
Compilation.PROCESS_ASSETS_STAGE_* constants are undefined, so plugins tap
processAssets at the wrong stage and silently drop index.html from the
packaged app.

Scope a fix to exactly the moment jiti is loaded: on win32 only,
temporarily wrap Module.createRequire so the require jiti builds for itself
gets a case-insensitive Proxy view over the same module cache, then restore
it. Only jiti's own require view is affected; the global require.cache is
never written or replaced. Off win32, jiti is loaded untouched.

Fixes #3949
@claude

claude Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Closing in favor of #4311. The createRequire-cache approach here didn't fix the issue on real Windows, so the new PR removes jiti from the equation entirely: TypeScript configs are now transpiled with the project's own typescript package and evaluated through Node's real module system, which guarantees a single shared instance of dependencies like webpack.

@claude claude Bot closed this Jul 1, 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.

Regression in v7.8.1: index.html no longer listed in Webpack assets

1 participant