Conversation
When a webpack host loads a Vite remote that shares the same React version, a circular deadlock can occur: the host's loadShare → initializeSharing awaits the Vite remote's init, but the remote's init eagerly runs __mfBridgeSharedProviders which calls loadShare back on the host, whose initializeSharing awaits the remote's still-pending init — a cycle that never resolves. The __mfLateBridgeShared deferral mechanism already exists and runs in getExposes(), but was only assigned conditionally when __mfUsesWebpackShareScope was true. This change assigns it unconditionally, deferring both __mfBridgeSharedProviders and the global instances bridging to getExposes() where all remote inits have already completed.
commit: |
gioboa
left a comment
There was a problem hiding this comment.
Skip the eager bridge in init() for webpack hosts; their loadShare() awaits this init, so bridging re-entered it and hung. Regression from #1115. Verified on the #1326 and #1064 repros, unit 1398, e2e 29/29, integration 28/28.
@RalphK66 thanks for the precise diagnosis and the repro, they made this easy to pin down. We're landing a narrower version of your fix: same idea, but the deferral only applies when the host's share scope is webpack's, so Vite hosts keep bridging inside init() as before.
Since module-federation#1275 a subpath share the host does not provide is seeded locally during init(), which evaluates the package's dev proxy before the late bridge runs. Dev proxies snapshot their exports, so the remote kept its own React copy under webpack/rspack hosts (hooks crash). Prod proxies rebind and are unchanged.
Guards the remote init() deadlock (module-federation#1326, module-federation#1064) and single-React rendering with same-version shared React.
|
@RalphK66 two more commits on top. While building an e2e for this I found that deferring the bridge alone leaves the remote on its own React copy with a Vite 6/7 dev remote (add a useState to your widget to see it), because a subpath share seeded during init() evaluates the react proxy before the bridge runs. The first commit skips that seeding in dev when the provider is still pending; the second adds a webpack + rspack host fixture so both the deadlock and single-React rendering stay covered. |
closes #1326
Summary
Fixes a deadlock where a webpack host loading Vite remotes that share the same React version hangs indefinitely —
import('./bootstrap')never resolves.Deadlock chain
import('./bootstrap')→ sharing-consume chunk →loadShare("react")→initializeSharing→ awaits all remote init promisesinit()eagerly runsawait __mfBridgeSharedProviders()→__mfBridgeExternalSharedProvider→loadShare(pkg)back on the hostloadShare→initializeSharing→ same cached promise → awaits the Vite remote's still-pendinginit()→ circular deadlockFix
The
__mfLateBridgeShareddeferral mechanism already exists and runs ingetExposes(). This change assigns it unconditionally instead of eagerly awaiting__mfBridgeSharedProviders()duringinit(). Both the materialized-batch bridging and the global-instances bridging are wrapped into a single deferred function. By the timegetExposes()runs, all remote inits have completed andinitializeSharingresolves without circular waits.Before (deadlocks):
After (defers):
Reproduction
Minimal repro: https://github.com/RalphK66/mf-vite-init-deadlock-repro
1 webpack host + 2 Vite remotes, all sharing React 19 at the same version. The host page deadlocks on every load —
import('./bootstrap')never resolves.Note on runtime-core
loadShare()in@module-federation/runtime-coreunconditionally awaitsinitializeSharing()which includes ALL remote init promises. Any remote that callsloadShareduring itsinit()can trigger this cycle. This PR breaks the cycle from the Vite side; the core runtime could independently be made more resilient.Test plan