Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-esbuild-import-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@module-federation/esbuild": patch
---

fix: fix import map registration for ESM remote modules

The import map registration was failing because the runtimePlugin was looking for moduleMap in the host scope instead of fetching it from the remote module exports. This fix adds explicit synchronous import map registration in the host initialization code that runs after initializeSharing but before any code that imports from remotes.
13 changes: 10 additions & 3 deletions apps/esbuild/build/build-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async function buildProject(projectName, watch) {

fs.rmSync(outputPath, { force: true, recursive: true });

await esbuild.build({
const buildOptions = {
entryPoints: [path.join(projectName, 'main.ts')],
outdir: outputPath,
bundle: true,
Expand All @@ -28,8 +28,15 @@ async function buildProject(projectName, watch) {
require(path.join('../', projectName, 'federation.config.js')),
),
],
watch,
});
};

if (watch) {
const ctx = await esbuild.context(buildOptions);
await ctx.watch();
console.log(`Watching ${projectName} for changes...`);
} else {
await esbuild.build(buildOptions);
}

['index.html', 'favicon.ico', 'styles.css'].forEach((file) => {
fs.copyFileSync(path.join(projectName, file), path.join(outputPath, file));
Expand Down
77 changes: 57 additions & 20 deletions packages/esbuild/src/adapters/lib/containerReference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,28 +59,38 @@ export const buildFederationHost = (config: NormalizedFederationConfig) => {
const runtimePlugin = () => ({
name: 'import-maps-plugin',
async init(args) {
const remotePrefetch = args.options.remotes.map(async (remote) => {
console.log('remote', remote);
if (remote.type === 'esm') {
await import(remote.entry);
}
});

await Promise.all(remotePrefetch);
if (typeof moduleMap !== 'undefined') {
const map = Object.keys(moduleMap).reduce((acc, expose) => {
const importMap = importShim.getImportMap().imports;
const key = args.origin.name + expose.replace('.', '');
if (!importMap[key]) {
const encodedModule = encodeInlineESM(
createVirtualRemoteModule(args.origin.name, key, moduleMap[expose].exports)
);
acc[key] = encodedModule;
// Load all remotes and collect their moduleMaps
const remotesWithModuleMaps = await Promise.all(
args.options.remotes.map(async (remote) => {
console.log('remote', remote);
if (remote.type === 'esm') {
const remoteModule = await import(remote.entry);
return { remote, moduleMap: remoteModule.moduleMap };
}
return { remote, moduleMap: null };
})
);

// Build import map entries for all remote modules
const allImports = {};
for (const { remote, moduleMap } of remotesWithModuleMaps) {
if (moduleMap && typeof moduleMap === 'object') {
for (const expose of Object.keys(moduleMap)) {
const currentImportMap = importShim.getImportMap().imports;
// Use remote name + expose path (e.g., "mfe1/component")
const key = remote.name + expose.replace('.', '');
if (!currentImportMap[key] && !allImports[key]) {
const encodedModule = encodeInlineESM(
createVirtualRemoteModule(remote.name, key, moduleMap[expose].exports)
);
allImports[key] = encodedModule;
}
}
return acc;
}, {});
}
}

await importShim.addImportMap({ imports: map });
if (Object.keys(allImports).length > 0) {
await importShim.addImportMap({ imports: allImports });
}

return args;
Expand All @@ -96,6 +106,33 @@ export const buildFederationHost = (config: NormalizedFederationConfig) => {

await Promise.all(mfHoZJ92.initializeSharing('default', 'version-first'));

// Ensure import maps are registered before any code that uses remotes runs
const remotesList = ${remoteConfigs};
const allImports = {};
for (const remote of remotesList) {
if (remote.type === 'esm') {
try {
const remoteModule = await import(remote.entry);
const moduleMap = remoteModule.moduleMap;
if (moduleMap && typeof moduleMap === 'object') {
for (const expose of Object.keys(moduleMap)) {
const key = remote.name + expose.replace('.', '');
if (!allImports[key]) {
const encodedModule = encodeInlineESM(
createVirtualRemoteModule(remote.name, key, moduleMap[expose].exports)
);
allImports[key] = encodedModule;
}
}
}
} catch (e) {
console.error('Failed to load remote:', remote.name, e);
}
}
}
if (Object.keys(allImports).length > 0) {
await importShim.addImportMap({ imports: allImports });
}

`;
};
Expand Down