fix: await pending share loads before entry import in bootstrap#866
Conversation
38cf655 to
aca5779
Compare
|
@gioboa please approve workflows |
commit: |
gioboa
left a comment
There was a problem hiding this comment.
Thanks 🙏
Do you have a repo to validate these changes?
No, im submitting this work based on my situation, tests (and a real integration test) were added to the PR. As I can see CI is oddly failing, ill convert this to a draft until thats solved. |
There was a problem hiding this comment.
These changes should fix the errors
index 4457b0c..a37b88b 100644
--- a/integration/shared-cache-deferred-exports.test.ts
+++ b/integration/shared-cache-deferred-exports.test.ts
@@ -117,8 +117,9 @@ describe('remote loadShare (import: false — host-provided)', () => {
expect(code).toContain('__mfApplyHostProvidedExports');
// The else branch (cache hit) must apply synchronously
- // Structure: if (exportModule === void 0) initPromise.then(...) else __mfApplyHostProvidedExports(exportModule)
- const elseMatch = code.match(/else\s+__mfApplyHostProvidedExports(exportModule)/);
-
// Structure: if (exportModule === void 0) initPromise.then(...) else { __mfApplyHostProvidedExports(exportModule) }
-
// Rollup/Vite versions differ on whether they keep braces or whitespace after
else. -
const elseMatch = code.match(/else\s*{?\s*__mfApplyHostProvidedExports(exportModule)/);
expect(elseMatch).not.toBeNull();// Must NOT push to pendingShareLoads in the else branch
@@ -200,8 +201,9 @@ describe('host loadShare (import: true — workspace singleton)', () => {
expect(code).toContain('__mfApplyLazyShareExports');// The else branch (cache hit) must apply synchronously
- // Structure: else __mfApplyLazyShareExports(exportModule)
- const elseMatch = code.match(/else\s+__mfApplyLazyShareExports(exportModule)/);
- // Structure: else { __mfApplyLazyShareExports(exportModule) }
- // Rollup/Vite versions differ on whether they keep braces or whitespace after
else. - const elseMatch = code.match(/else\s*{?\s*__mfApplyLazyShareExports(exportModule)/);
expect(elseMatch).not.toBeNull();
The bootstrap code generated by pluginAddEntry calls initHost() then immediately imports the entry module. However, some loadShare modules (workspace singletons with lazy fallback, and import:false modules) do not populate their exports synchronously during init(). Instead they schedule an initPromise.then() callback that dynamically imports the real module and assigns exports asynchronously. The bootstrap never waited for these callbacks to complete. This means the entry module (and any code it transitively runs) could access shared exports (e.g. react, react-dom) before the async initPromise.then() + import() chain resolved, reading undefined. The fix tracks these deferred share load promises in __mfModuleCache.pendingShareLoads. Two code paths are wrapped: 1. generateLazyWorkspaceSingletonExports (virtualShared_preBuild.ts): the client-side initPromise.then(() => import(...)) that assigns workspace singleton exports after the ESM import resolves. 2. generateDeferredHostProvidedExports (virtualShared_preBuild.ts): the initPromise.then() that waits for the host runtime to provide a shared module the remote does not have locally (import: false). The bootstrap in pluginAddEntry.ts now awaits Promise.all(__mfModuleCache.pendingShareLoads) after initHost() and before importing the entry.
aca5779 to
f09bf4a
Compare
|
@gioboa please re-run workflows |

The bootstrap code generated by pluginAddEntry calls initHost() then immediately imports the entry module. However, some loadShare modules (workspace singletons with lazy fallback, and import:false modules) do not populate their exports synchronously during init().
Instead they schedule an initPromise.then() callback that dynamically imports the real module and assigns exports asynchronously. The bootstrap never waited for these callbacks to complete.
This means the entry module (and any code it transitively runs) could access shared exports (e.g. react, react-dom) before the async initPromise.then() + import() chain resolved, reading undefined.
The fix tracks these deferred share load promises in __mfModuleCache.pendingShareLoads. Two code paths are wrapped:
generateLazyWorkspaceSingletonExports (virtualShared_preBuild.ts): the client-side initPromise.then(() => import(...)) that assigns workspace singleton exports after the ESM import resolves.
generateDeferredHostProvidedExports (virtualShared_preBuild.ts): the initPromise.then() that waits for the host runtime to provide a shared module the remote does not have locally (import: false).
The bootstrap in pluginAddEntry.ts now awaits
Promise.all(__mfModuleCache.pendingShareLoads) after initHost() and before importing the entry.