Skip to content

Commit 696a637

Browse files
committed
fix(webpack-bundler-runtime): override by dynamic to resolve mjs issue
fix mjs handling issue by introducing dynamic override
1 parent 30a4df4 commit 696a637

File tree

16 files changed

+154
-515
lines changed

16 files changed

+154
-515
lines changed

.changeset/early-eggs-attack.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@module-federation/webpack-bundler-runtime': patch
2+
'@module-federation/enhanced': patch
33
---
44

5-
Resolve module (mjs) correctly on runtime by changing consumes.ts and installInitialConsumes.ts
5+
Fix ESM default export handling for .mjs files by overriding getExportsType() in ConsumeSharedModule and RemoteModule to return "dynamic"

packages/enhanced/src/lib/container/RemoteModule.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ class RemoteModule extends Module {
147147
return 6;
148148
}
149149

150+
/**
151+
* @returns {string} the export type
152+
*
153+
* "dynamic" means: Check at runtime if __esModule is set.
154+
* When set: namespace = { ...exports, default: exports }
155+
* When not set: namespace = { default: exports }
156+
*/
157+
// @ts-ignore
158+
override getExportsType():
159+
| 'namespace'
160+
| 'default-only'
161+
| 'default-with-named'
162+
| 'dynamic' {
163+
return 'dynamic';
164+
}
165+
150166
/**
151167
* @returns {Set<string>} types available (do not mutate)
152168
*/

packages/enhanced/src/lib/sharing/ConsumeSharedModule.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,22 @@ class ConsumeSharedModule extends Module {
205205
return 42;
206206
}
207207

208+
/**
209+
* @returns {string} the export type
210+
*
211+
* "dynamic" means: Check at runtime if __esModule is set.
212+
* When set: namespace = { ...exports, default: exports }
213+
* When not set: namespace = { default: exports }
214+
*/
215+
// @ts-ignore
216+
override getExportsType():
217+
| 'namespace'
218+
| 'default-only'
219+
| 'default-with-named'
220+
| 'dynamic' {
221+
return 'dynamic';
222+
}
223+
208224
/**
209225
* @param {Hash} hash the hash used to track dependencies
210226
* @param {UpdateHashContext} context context

packages/enhanced/src/lib/sharing/ConsumeSharedPlugin.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -696,57 +696,6 @@ class ConsumeSharedPlugin {
696696
},
697697
);
698698

699-
// Add finishModules hook to copy buildMeta/buildInfo from fallback modules *after* webpack's export analysis
700-
// Running earlier causes failures, so we intentionally execute later than plugins like FlagDependencyExportsPlugin.
701-
// This still follows webpack's pattern used by FlagDependencyExportsPlugin and InferAsyncModulesPlugin, but with a
702-
// later stage. Based on webpack's Compilation.js: finishModules (line 2833) runs before seal (line 2920).
703-
compilation.hooks.finishModules.tapAsync(
704-
{
705-
name: PLUGIN_NAME,
706-
stage: 10, // Run after FlagDependencyExportsPlugin (default stage 0)
707-
},
708-
(modules, callback) => {
709-
for (const module of modules) {
710-
// Only process ConsumeSharedModule instances with fallback dependencies
711-
if (
712-
!(module instanceof ConsumeSharedModule) ||
713-
!module.options.import
714-
) {
715-
continue;
716-
}
717-
718-
let dependency;
719-
if (module.options.eager) {
720-
// For eager mode, get the fallback directly from dependencies
721-
dependency = module.dependencies[0];
722-
} else {
723-
// For async mode, get it from the async dependencies block
724-
dependency = module.blocks[0]?.dependencies[0];
725-
}
726-
727-
if (dependency) {
728-
const fallbackModule =
729-
compilation.moduleGraph.getModule(dependency);
730-
if (
731-
fallbackModule &&
732-
fallbackModule.buildMeta &&
733-
fallbackModule.buildInfo
734-
) {
735-
// Copy buildMeta and buildInfo following webpack's DelegatedModule pattern: this.buildMeta = { ...delegateData.buildMeta };
736-
// This ensures ConsumeSharedModule inherits ESM/CJS detection (exportsType) and other optimization metadata
737-
module.buildMeta = { ...fallbackModule.buildMeta };
738-
module.buildInfo = { ...fallbackModule.buildInfo };
739-
// Mark all exports as provided, to avoid webpack's export analysis from marking them as unused since we copy buildMeta
740-
compilation.moduleGraph
741-
.getExportsInfo(module)
742-
.setUnknownExportsProvided();
743-
}
744-
}
745-
}
746-
callback();
747-
},
748-
);
749-
750699
compilation.hooks.additionalTreeRuntimeRequirements.tap(
751700
PLUGIN_NAME,
752701
(chunk, set) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
it('should correctly handle default imports in .mjs files from remote modules', async () => {
2+
const { testDefaultImport } = await import('./pure-esm-consumer.mjs');
3+
const result = testDefaultImport();
4+
expect(result.defaultType).toBe('function');
5+
expect(result.defaultValue).toBe('remote default export');
6+
expect(result.namedExportValue).toBe('remote named export');
7+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import something from 'remote-esm-pkg/module';
2+
import { namedExport } from 'remote-esm-pkg/module';
3+
4+
export function testDefaultImport() {
5+
return {
6+
defaultType: typeof something,
7+
defaultValue: typeof something === 'function' ? something() : something,
8+
namedExportValue: namedExport,
9+
};
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = {
2+
moduleScope(scope) {
3+
scope.REMOTE_ESM_PKG = {
4+
get(module) {
5+
return new Promise((resolve) => {
6+
setTimeout(() => {
7+
resolve(() => ({
8+
__esModule: true,
9+
default: function remoteFunction() {
10+
return 'remote default export';
11+
},
12+
namedExport: 'remote named export',
13+
}));
14+
}, 100);
15+
});
16+
},
17+
};
18+
},
19+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { ContainerReferencePlugin } = require('../../../../dist/src');
2+
3+
module.exports = {
4+
mode: 'development',
5+
devtool: false,
6+
plugins: [
7+
new ContainerReferencePlugin({
8+
remoteType: 'var',
9+
remotes: {
10+
'remote-esm-pkg': 'REMOTE_ESM_PKG',
11+
},
12+
}),
13+
],
14+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
it('should correctly handle default imports in .mjs files from shared modules', async () => {
2+
await __webpack_init_sharing__('default');
3+
const { testDefaultImport } = await import('./pure-esm-consumer.mjs');
4+
const result = testDefaultImport();
5+
expect(result.defaultType).toBe('function');
6+
expect(result.defaultValue).toBe('shared default export');
7+
expect(result.namedExportValue).toBe('shared named export');
8+
});

packages/enhanced/test/configCases/sharing/consume-module-mjs-default-export/node_modules/shared-esm-pkg/index.js

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)