-
Notifications
You must be signed in to change notification settings - Fork 90
fix: Vite 7 Rolldown compatibility - isolate preload helper to prevent shared module deadlock #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
fix: Vite 7 Rolldown compatibility - isolate preload helper to prevent shared module deadlock #369
Conversation
…o ESM" - Changed virtual modules from CJS (require + module.exports) to ESM (await import + export) - Changed file extensions from .js to .mjs to ensure ESM treatment by bundlers - This fixes the 'await is only valid in async functions' syntax error when Rolldown wraps CJS modules in a function wrapper Fixes module-federation#320"
gioboa
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your help, in the next days I'm going to check this PR. 🙏
## Problem
Vite 7 (Rolldown) wraps CJS modules in __commonJSMin() arrow function,
making top-level await invalid ('await is only valid in async functions').
## Solution
- Dev mode: Keep original CJS syntax (backward compatible with Vite 5)
- Build mode: Use ESM (import/export) with top-level await
- runtimeInitStatus: Dual exports to support both modes
## Testing
Added new CI job to test production builds (vite build + preview),
which catches the Vite 7/Rolldown CJS wrapper issue.
Fixes module-federation#320
gioboa
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pcfreak30 @sabov can you double check this PR? Thanks 🙏
… TLA
The build was hanging due to a circular dependency where:
1. hostInit imports preload helper from a shared chunk
2. That chunk contains loadShare TLA waiting for initPromise
3. initPromise only resolves after remoteEntry.init() is called
4. hostInit can't call init() until the shared chunk loads → DEADLOCK
Changes:
- Isolate vite/preload-helper, vite/modulepreload-polyfill, and
commonjsHelpers into a separate 'preload-helper' chunk via manualChunks
- Fix shared module alias regex: patterns ending with '/' (e.g., 'react/')
now correctly match only subpaths ('react/jsx-runtime') and not the
base package ('react')
- Use globalThis singleton for initPromise to prevent duplicate instances
This ensures the preload helper is available before any TLA-blocked chunks
need to load, breaking the circular dependency.
|
Summary from my testing:
attached chunk below This can be reproduced with repo |
| } | ||
| // Call existing manualChunks if it exists | ||
| if (typeof existingManualChunks === 'function') { | ||
| return existingManualChunks(id, meta); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if this part could cause breaking changes for anyone relying on manual checks since a set of vite plugins (line 118-120) won't be checked anymore.
|
I've also encountered similar deadlock issues caused by circular dependencies, which I resolved by configuring The Module Federation initialization logic was bundled into another business chunk, creating a circular dependency: hostInit -> bizChunk -> remoteEntry -> hostInit, which in turn caused the initPromise to never be resolved. My solution was to package runtimeInit separately: // vite.config.ts
manualChunks: (id) => {
if (id.includes('Host__mf_v__runtimeInit__mf_v__')) {
// Module Federation runtime init chunk
return 'runtimeInit';
}
}But my thought is: if the circular dependency issue isn't caused by the module-federation library, it shouldn't be the library's responsibility to fix it either. Of course, not yet. |
I think in this case it's partially caused by the library's architecture. Maybe there is a way to change how remote modules are loaded to make sure the deadlock never happens regardless of how vite bundles things. E.g. instead of waiting for init(), we could queue loadShare requests immediately, execute the queue when init() is called and return a promise that resolves when the queue is processed. What do you think? |




TLDR change summary
manualChunkslogic to isolate Vite's preload helper into its own chunkProblem
When using
@module-federation/vitewith shared dependencies (e.g., React singleton), users encounter a circular deadlock that causes the app to hang silently on load.The deadlock sequence:
hostInitimports the preload helper_from a shared chunk (e.g.,mf-loadShare)initPromiseinitPromiseonly resolves whenremoteEntry.init()is calledhostInitcan't callinit()until it finishes loading → DEADLOCKRoot cause: Rollup/Vite bundles the preload helper into the same chunk as the loadShare TLA code because both are imported by multiple entry points.
Example of problematic chunk structure:
hostInit-xxx.js:
mf-loadShare-xxx.js:
Solution
Add
manualChunksconfiguration in the plugin'sconfighook to isolate the preload helper:Fixed chunk structure:
hostInit-xxx.js:
preload-helper-xxx.js:
mf-loadShare-xxx.js:
import('./bootstrap')pattern is still necessary to create an async boundary for MF runtime initializationTesting
Related Issues
Fixes #320