From 0573749cdff0bb839e59a723f3d58e94fa98698b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 8 Feb 2026 22:30:05 +0000 Subject: [PATCH 01/16] feat: add import map entry support --- .../lib/container/ModuleFederationPlugin.ts | 4 +++ .../container/ModuleFederationPlugin.check.ts | 25 ++++++++++++++ .../container/ModuleFederationPlugin.json | 4 +++ .../container/ModuleFederationPlugin.ts | 5 +++ packages/runtime-core/README.md | 24 +++++++++++++ .../__tests__/register-remotes.spec.ts | 34 +++++++++++++++++++ packages/runtime-core/src/remote/index.ts | 26 +++++++++++++- packages/runtime-core/src/type/config.ts | 4 +++ packages/runtime-core/src/utils/load.ts | 1 + .../types/plugins/ModuleFederationPlugin.ts | 4 +++ 10 files changed, 130 insertions(+), 1 deletion(-) diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index c9c4bacf0dc..67708150c34 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -77,6 +77,10 @@ class ModuleFederationPlugin implements WebpackPluginInstance { definePluginOptions['FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN'] = disableSnapshot; + const disableImportMap = + experiments?.optimization?.disableImportMap ?? false; + definePluginOptions['FEDERATION_OPTIMIZE_NO_IMPORTMAP'] = disableImportMap; + // Determine ENV_TARGET: only if manually specified in experiments.optimization.target if ( experiments?.optimization && diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts index 47c62bf5dac..679d9d2cce0 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.check.ts @@ -468,6 +468,7 @@ const t = { type: 'object', properties: { disableSnapshot: { type: 'boolean' }, + disableImportMap: { type: 'boolean' }, target: { enum: ['web', 'node'] }, }, additionalProperties: !1, @@ -4629,6 +4630,8 @@ function D( if ( 'disableSnapshot' !== e && + 'disableImportMap' !== + e && 'target' !== e ) return ( @@ -4664,6 +4667,28 @@ function D( ); var I = e === c; } else I = !0; + if (I) + if ( + void 0 !== + r.disableImportMap + ) { + const e = c; + if ( + 'boolean' != + typeof r.disableImportMap + ) + return ( + (D.errors = [ + { + params: { + type: 'boolean', + }, + }, + ]), + !1 + ); + I = e === c; + } else I = !0; if (I) if ( void 0 !== diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json index 4ca4c940283..7a7d0c26b62 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.json @@ -883,6 +883,10 @@ "description": "Enable optimization to skip snapshot plugin", "type": "boolean" }, + "disableImportMap": { + "description": "Enable optimization to skip import map support in runtime-core", + "type": "boolean" + }, "target": { "description": "Target environment for the build", "enum": ["web", "node"] diff --git a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts index 4cfba3f43aa..5884764e6dc 100644 --- a/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/schemas/container/ModuleFederationPlugin.ts @@ -977,6 +977,11 @@ export default { description: 'Enable optimization to skip snapshot plugin', type: 'boolean', }, + disableImportMap: { + description: + 'Enable optimization to skip import map support in runtime-core', + type: 'boolean', + }, target: { description: 'Target environment for the build', enum: ['web', 'node'], diff --git a/packages/runtime-core/README.md b/packages/runtime-core/README.md index 0f3baf61ab4..5ace521e957 100644 --- a/packages/runtime-core/README.md +++ b/packages/runtime-core/README.md @@ -8,6 +8,30 @@ See [https://module-federation.io/guide/basic/runtime/runtime.html](https://module-federation.io/guide/basic/runtime/runtime.html) for details. +## Import Maps + +When using Import Maps with `type: "module"` or `type: "system"` remotes, preserve bare specifiers by setting `entryFormat: "importmap"`: + +```ts +import { ModuleFederation } from '@module-federation/runtime-core'; + +const mf = new ModuleFederation({ + name: 'host', + remotes: [ + { + name: 'webpack_remote', + entry: 'webpack_remote', + type: 'module', + entryFormat: 'importmap', + }, + ], +}); +``` + +This keeps the entry untouched so the browser/SystemJS can resolve it via the import map. + +To tree-shake import map support, define `FEDERATION_OPTIMIZE_NO_IMPORTMAP` as `true` (or use `experiments.optimization.disableImportMap` when using the ModuleFederationPlugin). + ## License `@module-federation/runtime` is [MIT licensed](https://github.com/module-federation/core/blob/main/packages/runtime/LICENSE). diff --git a/packages/runtime-core/__tests__/register-remotes.spec.ts b/packages/runtime-core/__tests__/register-remotes.spec.ts index 174d914c491..1a8953b2e9e 100644 --- a/packages/runtime-core/__tests__/register-remotes.spec.ts +++ b/packages/runtime-core/__tests__/register-remotes.spec.ts @@ -102,4 +102,38 @@ describe('ModuleFederation', () => { // Value is different from the registered remote expect(newApp1Res).toBe('hello app1 entry2'); }); + + it('preserves import map entries when entryFormat is importmap', () => { + const entry = 'webpack_remote'; + const FM = new ModuleFederation({ + name: '@federation/instance', + remotes: [ + { + name: '@register-remotes/importmap', + entry, + entryFormat: 'importmap', + type: 'module', + }, + ], + }); + + expect(FM.options.remotes[0].entry).toBe(entry); + }); + + it('normalizes relative entries to absolute urls by default', () => { + const entry = '/static/remoteEntry.js'; + const FM = new ModuleFederation({ + name: '@federation/instance', + remotes: [ + { + name: '@register-remotes/relative', + entry, + }, + ], + }); + + expect(FM.options.remotes[0].entry).toBe( + new URL(entry, window.location.origin).href, + ); + }); }); diff --git a/packages/runtime-core/src/remote/index.ts b/packages/runtime-core/src/remote/index.ts index bb101f25450..9f6fbb56887 100644 --- a/packages/runtime-core/src/remote/index.ts +++ b/packages/runtime-core/src/remote/index.ts @@ -48,6 +48,14 @@ import { formatPreloadArgs, preloadAssets } from '../utils/preload'; import { getGlobalShareScope } from '../utils/share'; import { getGlobalRemoteInfo } from '../plugins/snapshot/SnapshotHandler'; +declare const FEDERATION_OPTIMIZE_NO_IMPORTMAP: boolean; +const USE_IMPORTMAP = + typeof FEDERATION_OPTIMIZE_NO_IMPORTMAP === 'boolean' + ? !FEDERATION_OPTIMIZE_NO_IMPORTMAP + : true; + +const IMPORTMAP_REMOTE_TYPES = new Set(['module', 'system']); + export interface LoadRemoteMatch { id: string; pkgNameOrAlias: string; @@ -426,7 +434,23 @@ export class RemoteHandler { } // Set the remote entry to a complete path if ('entry' in remote) { - if (isBrowserEnv() && !remote.entry.startsWith('http')) { + const preserveImportMapEntry = + USE_IMPORTMAP && remote.entryFormat === 'importmap'; + if ( + preserveImportMapEntry && + !IMPORTMAP_REMOTE_TYPES.has(remote.type || DEFAULT_REMOTE_TYPE) + ) { + warn( + `Remote "${remote.name}" uses entryFormat="importmap" but remote type "${ + remote.type || DEFAULT_REMOTE_TYPE + }" does not support import maps. Use type "module" or "system" to enable import map resolution.`, + ); + } + if ( + isBrowserEnv() && + !preserveImportMapEntry && + !remote.entry.startsWith('http') + ) { remote.entry = new URL(remote.entry, window.location.origin).href; } } diff --git a/packages/runtime-core/src/type/config.ts b/packages/runtime-core/src/type/config.ts index 98c6c357bbf..4b78a030e39 100644 --- a/packages/runtime-core/src/type/config.ts +++ b/packages/runtime-core/src/type/config.ts @@ -12,11 +12,14 @@ export type PartialOptional = Omit & { [P in K]-?: T[P]; }; +export type RemoteEntryFormat = 'url' | 'importmap'; + export interface RemoteInfoCommon { alias?: string; shareScope?: string | string[]; type?: RemoteEntryType; entryGlobalName?: string; + entryFormat?: RemoteEntryFormat; } export type RemoteInfoOptionalVersion = { @@ -40,6 +43,7 @@ export interface RemoteInfo { type: RemoteEntryType; entryGlobalName: string; shareScope: string | string[]; + entryFormat?: RemoteEntryFormat; } export type HostInfo = Pick< diff --git a/packages/runtime-core/src/utils/load.ts b/packages/runtime-core/src/utils/load.ts index 2c315935f82..be2663c943f 100644 --- a/packages/runtime-core/src/utils/load.ts +++ b/packages/runtime-core/src/utils/load.ts @@ -320,5 +320,6 @@ export function getRemoteInfo(remote: Remote): RemoteInfo { type: remote.type || DEFAULT_REMOTE_TYPE, entryGlobalName: remote.entryGlobalName || remote.name, shareScope: remote.shareScope || DEFAULT_SCOPE, + entryFormat: remote.entryFormat, }; } diff --git a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts index 80da0aaa856..b12a6a8b327 100644 --- a/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts +++ b/packages/sdk/src/types/plugins/ModuleFederationPlugin.ts @@ -263,6 +263,10 @@ export interface ModuleFederationPluginOptions { * Enable optimization to skip snapshot plugin */ disableSnapshot?: boolean; + /** + * Enable optimization to skip import map support in runtime-core + */ + disableImportMap?: boolean; /** * Target environment for the build */ From 4c9e132fa08d152c41cb591c375752d1c3a393a0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 8 Feb 2026 22:36:23 +0000 Subject: [PATCH 02/16] docs: add import map optimization flag --- .../docs/en/configure/experiments.mdx | 13 +++++++ .../docs/zh/configure/experiments.mdx | 35 ++++++++++++++++++- packages/rspack/src/ModuleFederationPlugin.ts | 4 +++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/apps/website-new/docs/en/configure/experiments.mdx b/apps/website-new/docs/en/configure/experiments.mdx index 2eca65496b6..e0e5d95ccee 100644 --- a/apps/website-new/docs/en/configure/experiments.mdx +++ b/apps/website-new/docs/en/configure/experiments.mdx @@ -13,6 +13,7 @@ new ModuleFederationPlugin({ provideExternalRuntime: false, optimization: { disableSnapshot: false, + disableImportMap: false, target: 'web', }, }, @@ -87,6 +88,18 @@ When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLU **Caution:** Setting `disableSnapshot: true` will disable the mf-manifest protocol. This means you will lose TypeScript syncing support and hot module replacement (HMR) for federated modules. If you are only using `.js` remotes (not manifest-based remotes), you will not lose any functionality as the `js` remotes do not support these capabilities anyways. ::: +### disableImportMap + +- **Type:** `boolean` +- **Required:** No +- **Default:** `false` + +When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_IMPORTMAP` global constant as `true` during the build. In `@module-federation/runtime-core`, this removes the import map entry handling so `entryFormat: "importmap"` remotes are ignored and any related checks are tree-shaken away. + +**Impact:** +* **Benefit:** Reduces runtime-core size when you do not use import maps. +* **Cost:** Disables support for bare-specifier import map entries (e.g., `entryFormat: "importmap"` with `type: "module"` or `type: "system"`). + ### target - **Type:** `'web' | 'node'` diff --git a/apps/website-new/docs/zh/configure/experiments.mdx b/apps/website-new/docs/zh/configure/experiments.mdx index bccab45771c..6155ee102a7 100644 --- a/apps/website-new/docs/zh/configure/experiments.mdx +++ b/apps/website-new/docs/zh/configure/experiments.mdx @@ -10,7 +10,12 @@ new ModuleFederationPlugin({ experiments: { asyncStartup: true, externalRuntime: false, - provideExternalRuntime: false + provideExternalRuntime: false, + optimization: { + disableSnapshot: false, + disableImportMap: false, + target: 'web', + }, }, shared: { react: { @@ -59,3 +64,31 @@ new ModuleFederationPlugin({ ::: 设置 `true` 后会在消费者处注入 MF runtime。 + +## optimization + +该对象包含与构建时优化相关的开关,会影响 Module Federation runtime 的体积和行为。 + +### disableSnapshot + +- Type: `boolean` +- Required: No +- Default: `false` + +设置为 `true` 会定义全局常量 `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN`,在 `@module-federation/runtime-core` 中移除 snapshot 插件与预加载插件逻辑,从而减小 runtime 体积。 + +### disableImportMap + +- Type: `boolean` +- Required: No +- Default: `false` + +设置为 `true` 会定义全局常量 `FEDERATION_OPTIMIZE_NO_IMPORTMAP`,在 `@module-federation/runtime-core` 中移除 import map 入口处理逻辑,适用于不使用 import maps 的场景。 + +### target + +- Type: `'web' | 'node'` +- Required: No +- Default: 从 Webpack `target` 推断(通常是 `'web'`) + +用于定义 `ENV_TARGET`,以启用针对 web/node 的树摇优化与运行时逻辑分支裁剪。 diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index 4ffa55fc6ae..4865da908f5 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -54,6 +54,10 @@ export class ModuleFederationPlugin implements RspackPluginInstance { definePluginOptions['FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN'] = disableSnapshot; + const disableImportMap = + experiments?.optimization?.disableImportMap ?? false; + definePluginOptions['FEDERATION_OPTIMIZE_NO_IMPORTMAP'] = disableImportMap; + // Determine ENV_TARGET: only if manually specified in experiments.optimization.target if ( experiments?.optimization && From ce7f19cae8e46ee947bc35482b2ec185e9129263 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 8 Feb 2026 22:39:37 +0000 Subject: [PATCH 03/16] docs: document import map optimization --- arch-doc/architecture-overview.md | 2 ++ arch-doc/runtime-architecture.md | 17 ++++++++++++++++- arch-doc/sdk-reference.md | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/arch-doc/architecture-overview.md b/arch-doc/architecture-overview.md index 90381b575a2..bed187df04a 100644 --- a/arch-doc/architecture-overview.md +++ b/arch-doc/architecture-overview.md @@ -554,6 +554,8 @@ graph TB style OptCheck fill:#f96,stroke:#333,stroke-width:2px ``` +Import map entry preservation is controlled separately via `FEDERATION_OPTIMIZE_NO_IMPORTMAP`, allowing builds that do not use import maps to tree-shake that logic from runtime-core. + ### Share Scope Management ```mermaid diff --git a/arch-doc/runtime-architecture.md b/arch-doc/runtime-architecture.md index a9552c4a3b6..d9b7dfce3d4 100644 --- a/arch-doc/runtime-architecture.md +++ b/arch-doc/runtime-architecture.md @@ -75,6 +75,19 @@ const USE_SNAPSHOT = When `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN` is `true`, snapshot functionality is disabled for smaller bundle sizes. +Import map entry preservation is controlled by the `FEDERATION_OPTIMIZE_NO_IMPORTMAP` build-time flag: + +```typescript +// Declared in remote/index.ts with DefinePlugin +declare const FEDERATION_OPTIMIZE_NO_IMPORTMAP: boolean; +const USE_IMPORTMAP = + typeof FEDERATION_OPTIMIZE_NO_IMPORTMAP === 'boolean' + ? !FEDERATION_OPTIMIZE_NO_IMPORTMAP + : true; // Default to true (enable import map support) +``` + +When `FEDERATION_OPTIMIZE_NO_IMPORTMAP` is `true`, import map-specific handling is tree-shaken from runtime-core. + ```mermaid classDiagram class ModuleFederation { @@ -1037,7 +1050,7 @@ class ViteBundlerRuntime implements BundlerRuntimeIntegration { ### Build-Time Responsibilities The build-time layer handles: -- **DefinePlugin Integration**: Defines `FEDERATION_BUILD_IDENTIFIER` and `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN` flags +- **DefinePlugin Integration**: Defines `FEDERATION_BUILD_IDENTIFIER`, `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN`, and `FEDERATION_OPTIMIZE_NO_IMPORTMAP` flags - **Bundle Generation**: Creates remote entry files and module manifests - **Static Analysis**: Determines shared dependencies and remote configurations - **Code Splitting**: Separates remote modules from host bundles @@ -1057,10 +1070,12 @@ The runtime layer handles: // Build-time defines these globals, runtime consumes them declare const FEDERATION_BUILD_IDENTIFIER: string; declare const FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN: boolean; +declare const FEDERATION_OPTIMIZE_NO_IMPORTMAP: boolean; // Runtime uses build-time generated information const buildId = getBuilderId(); // Reads FEDERATION_BUILD_IDENTIFIER const useSnapshot = !FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLUGIN; // Feature flag +const useImportMap = !FEDERATION_OPTIMIZE_NO_IMPORTMAP; // Feature flag // Build-time generates manifest, runtime consumes it const manifest = await fetch('./federation-manifest.json'); diff --git a/arch-doc/sdk-reference.md b/arch-doc/sdk-reference.md index ecba49c6060..854356709fa 100644 --- a/arch-doc/sdk-reference.md +++ b/arch-doc/sdk-reference.md @@ -109,6 +109,10 @@ interface ModuleFederationPluginOptions { * Enable optimization to skip snapshot plugin */ disableSnapshot?: boolean; + /** + * Enable optimization to skip import map support in runtime-core + */ + disableImportMap?: boolean; /** * Target environment for the build */ From 578bd15dc8cd3c5bc0189b8838b5c8b074376c82 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 8 Feb 2026 22:42:19 +0000 Subject: [PATCH 04/16] chore: disable import map in bundle-size config --- apps/bundle-size/webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/bundle-size/webpack.config.js b/apps/bundle-size/webpack.config.js index 665d2e94a91..0bd09cd5395 100644 --- a/apps/bundle-size/webpack.config.js +++ b/apps/bundle-size/webpack.config.js @@ -21,6 +21,7 @@ module.exports = composePlugins(withNx(), withReact(), (config, context) => { asyncStartup: true, optimization: { disableSnapshot: true, + disableImportMap: true, target: 'web', }, }, From 0895e94fac57f965103e85d964b4ff95dda37f05 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 8 Feb 2026 22:45:32 +0000 Subject: [PATCH 05/16] chore: add changeset for import map support --- .changeset/clean-cobras-rest.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/clean-cobras-rest.md diff --git a/.changeset/clean-cobras-rest.md b/.changeset/clean-cobras-rest.md new file mode 100644 index 00000000000..bbb81d571ec --- /dev/null +++ b/.changeset/clean-cobras-rest.md @@ -0,0 +1,10 @@ +--- +"@module-federation/runtime-core": patch +"@module-federation/enhanced": patch +"@module-federation/rspack": patch +"@module-federation/sdk": patch +--- + +Add import map remote entry support in runtime-core with a tree-shakeable +`FEDERATION_OPTIMIZE_NO_IMPORTMAP` flag, and expose `disableImportMap` +in Module Federation plugin optimization options. From afe5b54a624bf906190308f3b558f8491715b6af Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 00:07:17 +0000 Subject: [PATCH 06/16] feat: default disable import map in plugins --- .changeset/clean-cobras-rest.md | 3 ++- apps/website-new/docs/en/configure/experiments.mdx | 6 +++--- apps/website-new/docs/zh/configure/experiments.mdx | 6 +++--- .../enhanced/src/lib/container/ModuleFederationPlugin.ts | 2 +- packages/rspack/src/ModuleFederationPlugin.ts | 2 +- packages/runtime-core/README.md | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.changeset/clean-cobras-rest.md b/.changeset/clean-cobras-rest.md index bbb81d571ec..bc755640408 100644 --- a/.changeset/clean-cobras-rest.md +++ b/.changeset/clean-cobras-rest.md @@ -7,4 +7,5 @@ Add import map remote entry support in runtime-core with a tree-shakeable `FEDERATION_OPTIMIZE_NO_IMPORTMAP` flag, and expose `disableImportMap` -in Module Federation plugin optimization options. +in Module Federation plugin optimization options (defaulting to `true` +in build plugins). diff --git a/apps/website-new/docs/en/configure/experiments.mdx b/apps/website-new/docs/en/configure/experiments.mdx index e0e5d95ccee..8278b511161 100644 --- a/apps/website-new/docs/en/configure/experiments.mdx +++ b/apps/website-new/docs/en/configure/experiments.mdx @@ -13,7 +13,7 @@ new ModuleFederationPlugin({ provideExternalRuntime: false, optimization: { disableSnapshot: false, - disableImportMap: false, + disableImportMap: true, target: 'web', }, }, @@ -92,9 +92,9 @@ When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_SNAPSHOT_PLU - **Type:** `boolean` - **Required:** No -- **Default:** `false` +- **Default:** `true` -When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_IMPORTMAP` global constant as `true` during the build. In `@module-federation/runtime-core`, this removes the import map entry handling so `entryFormat: "importmap"` remotes are ignored and any related checks are tree-shaken away. +When set to `true`, this option defines the `FEDERATION_OPTIMIZE_NO_IMPORTMAP` global constant as `true` during the build. In `@module-federation/runtime-core`, this removes the import map entry handling so `entryFormat: "importmap"` remotes are ignored and any related checks are tree-shaken away. Set it to `false` to enable import map support. **Impact:** * **Benefit:** Reduces runtime-core size when you do not use import maps. diff --git a/apps/website-new/docs/zh/configure/experiments.mdx b/apps/website-new/docs/zh/configure/experiments.mdx index 6155ee102a7..82a6d97cdea 100644 --- a/apps/website-new/docs/zh/configure/experiments.mdx +++ b/apps/website-new/docs/zh/configure/experiments.mdx @@ -13,7 +13,7 @@ new ModuleFederationPlugin({ provideExternalRuntime: false, optimization: { disableSnapshot: false, - disableImportMap: false, + disableImportMap: true, target: 'web', }, }, @@ -81,9 +81,9 @@ new ModuleFederationPlugin({ - Type: `boolean` - Required: No -- Default: `false` +- Default: `true` -设置为 `true` 会定义全局常量 `FEDERATION_OPTIMIZE_NO_IMPORTMAP`,在 `@module-federation/runtime-core` 中移除 import map 入口处理逻辑,适用于不使用 import maps 的场景。 +设置为 `true` 会定义全局常量 `FEDERATION_OPTIMIZE_NO_IMPORTMAP`,在 `@module-federation/runtime-core` 中移除 import map 入口处理逻辑,适用于不使用 import maps 的场景。需要 import map 支持时请将其设置为 `false`。 ### target diff --git a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts index 67708150c34..5d2b802718a 100644 --- a/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts +++ b/packages/enhanced/src/lib/container/ModuleFederationPlugin.ts @@ -78,7 +78,7 @@ class ModuleFederationPlugin implements WebpackPluginInstance { disableSnapshot; const disableImportMap = - experiments?.optimization?.disableImportMap ?? false; + experiments?.optimization?.disableImportMap ?? true; definePluginOptions['FEDERATION_OPTIMIZE_NO_IMPORTMAP'] = disableImportMap; // Determine ENV_TARGET: only if manually specified in experiments.optimization.target diff --git a/packages/rspack/src/ModuleFederationPlugin.ts b/packages/rspack/src/ModuleFederationPlugin.ts index 4865da908f5..0c52a98ed80 100644 --- a/packages/rspack/src/ModuleFederationPlugin.ts +++ b/packages/rspack/src/ModuleFederationPlugin.ts @@ -55,7 +55,7 @@ export class ModuleFederationPlugin implements RspackPluginInstance { disableSnapshot; const disableImportMap = - experiments?.optimization?.disableImportMap ?? false; + experiments?.optimization?.disableImportMap ?? true; definePluginOptions['FEDERATION_OPTIMIZE_NO_IMPORTMAP'] = disableImportMap; // Determine ENV_TARGET: only if manually specified in experiments.optimization.target diff --git a/packages/runtime-core/README.md b/packages/runtime-core/README.md index 5ace521e957..0a057efff29 100644 --- a/packages/runtime-core/README.md +++ b/packages/runtime-core/README.md @@ -30,7 +30,7 @@ const mf = new ModuleFederation({ This keeps the entry untouched so the browser/SystemJS can resolve it via the import map. -To tree-shake import map support, define `FEDERATION_OPTIMIZE_NO_IMPORTMAP` as `true` (or use `experiments.optimization.disableImportMap` when using the ModuleFederationPlugin). +To tree-shake import map support, define `FEDERATION_OPTIMIZE_NO_IMPORTMAP` as `true` (or use `experiments.optimization.disableImportMap` when using the ModuleFederationPlugin). Note that the build plugins default `disableImportMap` to `true`, so set it to `false` if you want import map support enabled. ## License From 56133233bffa1fde8f2817e3831c1b81dcf81c85 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 00:17:49 +0000 Subject: [PATCH 07/16] feat: add import map demo apps --- apps/import-map/README.md | 23 ++++ apps/import-map/app1/cypress.config.ts | 12 ++ .../app1/cypress/e2e/import-map.cy.ts | 10 ++ .../app1/cypress/fixtures/example.json | 3 + .../app1/cypress/support/commands.ts | 14 ++ apps/import-map/app1/cypress/support/e2e.ts | 6 + apps/import-map/app1/cypress/tsconfig.json | 20 +++ apps/import-map/app1/package.json | 8 ++ apps/import-map/app1/project.json | 130 ++++++++++++++++++ apps/import-map/app1/src/index.html | 21 +++ apps/import-map/app1/src/index.ts | 48 +++++++ apps/import-map/app1/tsconfig.app.json | 16 +++ apps/import-map/app1/tsconfig.json | 19 +++ apps/import-map/app1/webpack.config.js | 19 +++ apps/import-map/app2/package.json | 8 ++ apps/import-map/app2/project.json | 71 ++++++++++ apps/import-map/app2/src/hello.ts | 3 + apps/import-map/app2/src/index.html | 10 ++ apps/import-map/app2/src/index.ts | 5 + apps/import-map/app2/tsconfig.app.json | 16 +++ apps/import-map/app2/tsconfig.json | 19 +++ apps/import-map/app2/webpack.config.js | 44 ++++++ 22 files changed, 525 insertions(+) create mode 100644 apps/import-map/README.md create mode 100644 apps/import-map/app1/cypress.config.ts create mode 100644 apps/import-map/app1/cypress/e2e/import-map.cy.ts create mode 100644 apps/import-map/app1/cypress/fixtures/example.json create mode 100644 apps/import-map/app1/cypress/support/commands.ts create mode 100644 apps/import-map/app1/cypress/support/e2e.ts create mode 100644 apps/import-map/app1/cypress/tsconfig.json create mode 100644 apps/import-map/app1/package.json create mode 100644 apps/import-map/app1/project.json create mode 100644 apps/import-map/app1/src/index.html create mode 100644 apps/import-map/app1/src/index.ts create mode 100644 apps/import-map/app1/tsconfig.app.json create mode 100644 apps/import-map/app1/tsconfig.json create mode 100644 apps/import-map/app1/webpack.config.js create mode 100644 apps/import-map/app2/package.json create mode 100644 apps/import-map/app2/project.json create mode 100644 apps/import-map/app2/src/hello.ts create mode 100644 apps/import-map/app2/src/index.html create mode 100644 apps/import-map/app2/src/index.ts create mode 100644 apps/import-map/app2/tsconfig.app.json create mode 100644 apps/import-map/app2/tsconfig.json create mode 100644 apps/import-map/app2/webpack.config.js diff --git a/apps/import-map/README.md b/apps/import-map/README.md new file mode 100644 index 00000000000..f38a765d9d6 --- /dev/null +++ b/apps/import-map/README.md @@ -0,0 +1,23 @@ +# Import Map Runtime Demo + +This example demonstrates using `@module-federation/runtime-core` with an +import map. The host (app1) loads the remote (app2) by using a bare specifier +mapped in the import map. + +## Running the demo + +Start both apps in separate terminals: + +```bash +pnpm nx run import-map-app2:serve +pnpm nx run import-map-app1:serve +``` + +- Host: http://127.0.0.1:3101 +- Remote entry (import map target): http://127.0.0.1:3102/remoteEntry.js + +## E2E + +```bash +pnpm nx run import-map-app1:test:e2e +``` diff --git a/apps/import-map/app1/cypress.config.ts b/apps/import-map/app1/cypress.config.ts new file mode 100644 index 00000000000..ff1f7f806a2 --- /dev/null +++ b/apps/import-map/app1/cypress.config.ts @@ -0,0 +1,12 @@ +import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; +import { defineConfig } from 'cypress'; + +export default defineConfig({ + e2e: { + ...nxE2EPreset(__filename, { cypressDir: 'cypress' }), + // Please ensure you use `cy.origin()` when navigating between domains and remove this option. + // See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin + injectDocumentDomain: true, + }, + defaultCommandTimeout: 20000, +}); diff --git a/apps/import-map/app1/cypress/e2e/import-map.cy.ts b/apps/import-map/app1/cypress/e2e/import-map.cy.ts new file mode 100644 index 00000000000..0288177eacc --- /dev/null +++ b/apps/import-map/app1/cypress/e2e/import-map.cy.ts @@ -0,0 +1,10 @@ +describe('import-map runtime host', () => { + beforeEach(() => cy.visit('/')); + + it('loads the remote module via import map', () => { + cy.get('[data-test="status"]').contains('Loaded'); + cy.get('[data-test="remote-message"]').contains( + 'Hello from import map remote', + ); + }); +}); diff --git a/apps/import-map/app1/cypress/fixtures/example.json b/apps/import-map/app1/cypress/fixtures/example.json new file mode 100644 index 00000000000..63f783087b5 --- /dev/null +++ b/apps/import-map/app1/cypress/fixtures/example.json @@ -0,0 +1,3 @@ +{ + "name": "import-map" +} diff --git a/apps/import-map/app1/cypress/support/commands.ts b/apps/import-map/app1/cypress/support/commands.ts new file mode 100644 index 00000000000..7d07d532499 --- /dev/null +++ b/apps/import-map/app1/cypress/support/commands.ts @@ -0,0 +1,14 @@ +/// + +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace Cypress { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface Chainable { + login(email: string, password: string): void; + } +} + +Cypress.Commands.add('login', (email, password) => { + void email; + void password; +}); diff --git a/apps/import-map/app1/cypress/support/e2e.ts b/apps/import-map/app1/cypress/support/e2e.ts new file mode 100644 index 00000000000..e58203ee857 --- /dev/null +++ b/apps/import-map/app1/cypress/support/e2e.ts @@ -0,0 +1,6 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// *********************************************************** + +import './commands'; diff --git a/apps/import-map/app1/cypress/tsconfig.json b/apps/import-map/app1/cypress/tsconfig.json new file mode 100644 index 00000000000..534d462fbc1 --- /dev/null +++ b/apps/import-map/app1/cypress/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "allowJs": true, + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["cypress", "node"], + "sourceMap": false + }, + "include": [ + "**/*.ts", + "**/*.js", + "../cypress.config.ts", + "../**/*.cy.ts", + "../**/*.cy.tsx", + "../**/*.cy.js", + "../**/*.cy.jsx", + "../**/*.d.ts" + ] +} diff --git a/apps/import-map/app1/package.json b/apps/import-map/app1/package.json new file mode 100644 index 00000000000..3f70b2341a1 --- /dev/null +++ b/apps/import-map/app1/package.json @@ -0,0 +1,8 @@ +{ + "name": "import-map-app1", + "private": true, + "version": "0.0.0", + "devDependencies": { + "@module-federation/runtime-core": "workspace:*" + } +} diff --git a/apps/import-map/app1/project.json b/apps/import-map/app1/project.json new file mode 100644 index 00000000000..2f92ed51be2 --- /dev/null +++ b/apps/import-map/app1/project.json @@ -0,0 +1,130 @@ +{ + "name": "import-map-app1", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/import-map/app1/src", + "projectType": "application", + "tags": [], + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "tsc", + "outputPath": "apps/import-map/app1/dist", + "index": "apps/import-map/app1/src/index.html", + "baseHref": "/", + "main": "apps/import-map/app1/src/index.ts", + "tsConfig": "apps/import-map/app1/tsconfig.app.json", + "styles": [], + "scripts": [], + "webpackConfig": "apps/import-map/app1/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": false, + "vendorChunk": false + } + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + }, + "serve": { + "executor": "@nx/webpack:dev-server", + "defaultConfiguration": "production", + "options": { + "buildTarget": "import-map-app1:build", + "hmr": true, + "port": 3101 + }, + "configurations": { + "development": { + "buildTarget": "import-map-app1:build:development" + }, + "production": { + "buildTarget": "import-map-app1:build:production", + "hmr": false + } + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + }, + "serve-static": { + "executor": "@nx/web:file-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "import-map-app1:build", + "port": 3101 + }, + "configurations": { + "development": { + "buildTarget": "import-map-app1:build:development" + }, + "production": { + "buildTarget": "import-map-app1:build:production" + } + } + }, + "e2e": { + "executor": "@nx/cypress:cypress", + "options": { + "cypressConfig": "apps/import-map/app1/cypress.config.ts", + "testingType": "e2e", + "baseUrl": "http://127.0.0.1:3101", + "browser": "chrome" + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ], + "configurations": { + "development": { + "runnerUi": true, + "browser": "electron", + "exit": false, + "watch": true + } + } + }, + "test:e2e": { + "executor": "nx:run-commands", + "options": { + "parallel": false, + "commands": [ + { + "command": "nx run import-map-app2:build", + "forwardAllArgs": false + }, + { + "command": "nx run import-map-app1:build", + "forwardAllArgs": false + }, + { + "command": "nx run import-map-app2:serve & nx run import-map-app1:serve & sleep 10 && nx run import-map-app1:e2e", + "forwardAllArgs": false + } + ] + } + } + } +} diff --git a/apps/import-map/app1/src/index.html b/apps/import-map/app1/src/index.html new file mode 100644 index 00000000000..8b4836542de --- /dev/null +++ b/apps/import-map/app1/src/index.html @@ -0,0 +1,21 @@ + + + + + Import Map Runtime Host + + + +
+

Import Map Runtime Host

+

Idle

+

Waiting for remote...

+
+ + diff --git a/apps/import-map/app1/src/index.ts b/apps/import-map/app1/src/index.ts new file mode 100644 index 00000000000..bdc74cdfbdd --- /dev/null +++ b/apps/import-map/app1/src/index.ts @@ -0,0 +1,48 @@ +import { ModuleFederation } from '@module-federation/runtime-core'; + +const statusEl = document.querySelector('[data-test="status"]'); +const messageEl = document.querySelector('[data-test="remote-message"]'); + +const setStatus = (message: string) => { + if (statusEl) { + statusEl.textContent = message; + } +}; + +const setMessage = (message: string) => { + if (messageEl) { + messageEl.textContent = message; + } +}; + +const federation = new ModuleFederation({ + name: 'import-map-host', + remotes: [ + { + name: 'import_map_remote', + entry: 'import_map_remote', + type: 'module', + entryFormat: 'importmap', + }, + ], + shared: {}, +}); + +const loadRemoteMessage = async () => { + try { + setStatus('Loading remote...'); + const remoteModule = await federation.loadRemote<{ + default?: () => string; + }>('import_map_remote/hello'); + + const message = + remoteModule?.default?.() ?? 'Remote module did not return a message.'; + setMessage(message); + setStatus('Loaded'); + } catch (error) { + setStatus('Failed'); + setMessage(`Error loading remote: ${String(error)}`); + } +}; + +void loadRemoteMessage(); diff --git a/apps/import-map/app1/tsconfig.app.json b/apps/import-map/app1/tsconfig.app.json new file mode 100644 index 00000000000..d5657e0790f --- /dev/null +++ b/apps/import-map/app1/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.js", + "**/*.test.js", + "dist/**" + ], + "include": ["**/*.ts"] +} diff --git a/apps/import-map/app1/tsconfig.json b/apps/import-map/app1/tsconfig.json new file mode 100644 index 00000000000..4ee1ce40929 --- /dev/null +++ b/apps/import-map/app1/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/apps/import-map/app1/webpack.config.js b/apps/import-map/app1/webpack.config.js new file mode 100644 index 00000000000..59bea61ad62 --- /dev/null +++ b/apps/import-map/app1/webpack.config.js @@ -0,0 +1,19 @@ +const { composePlugins, withNx } = require('@nx/webpack'); + +module.exports = composePlugins(withNx(), (config) => { + config.devtool = false; + config.experiments = { ...(config.experiments || {}), outputModule: true }; + config.output = { + ...config.output, + module: true, + scriptType: 'module', + publicPath: 'http://127.0.0.1:3101/', + }; + config.optimization = { + ...config.optimization, + runtimeChunk: false, + splitChunks: false, + }; + + return config; +}); diff --git a/apps/import-map/app2/package.json b/apps/import-map/app2/package.json new file mode 100644 index 00000000000..9b6440090b7 --- /dev/null +++ b/apps/import-map/app2/package.json @@ -0,0 +1,8 @@ +{ + "name": "import-map-app2", + "private": true, + "version": "0.0.0", + "devDependencies": { + "@module-federation/enhanced": "workspace:*" + } +} diff --git a/apps/import-map/app2/project.json b/apps/import-map/app2/project.json new file mode 100644 index 00000000000..7ac94b700e6 --- /dev/null +++ b/apps/import-map/app2/project.json @@ -0,0 +1,71 @@ +{ + "name": "import-map-app2", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/import-map/app2/src", + "projectType": "application", + "tags": [], + "targets": { + "build": { + "executor": "@nx/webpack:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "tsc", + "outputPath": "apps/import-map/app2/dist", + "index": "apps/import-map/app2/src/index.html", + "baseHref": "/", + "main": "apps/import-map/app2/src/index.ts", + "tsConfig": "apps/import-map/app2/tsconfig.app.json", + "styles": [], + "scripts": [], + "webpackConfig": "apps/import-map/app2/webpack.config.js" + }, + "configurations": { + "development": { + "extractLicenses": false, + "optimization": false, + "sourceMap": true, + "vendorChunk": true + }, + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": false, + "vendorChunk": false + } + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + }, + "serve": { + "executor": "@nx/webpack:dev-server", + "defaultConfiguration": "production", + "options": { + "buildTarget": "import-map-app2:build", + "hmr": true, + "port": 3102 + }, + "configurations": { + "development": { + "buildTarget": "import-map-app2:build:development" + }, + "production": { + "buildTarget": "import-map-app2:build:production", + "hmr": false + } + }, + "dependsOn": [ + { + "target": "build", + "dependencies": true + } + ] + } + } +} diff --git a/apps/import-map/app2/src/hello.ts b/apps/import-map/app2/src/hello.ts new file mode 100644 index 00000000000..0bd2d9a4fc9 --- /dev/null +++ b/apps/import-map/app2/src/hello.ts @@ -0,0 +1,3 @@ +const hello = () => 'Hello from import map remote'; + +export default hello; diff --git a/apps/import-map/app2/src/index.html b/apps/import-map/app2/src/index.html new file mode 100644 index 00000000000..9b0aee88294 --- /dev/null +++ b/apps/import-map/app2/src/index.html @@ -0,0 +1,10 @@ + + + + + Import Map Remote + + +
Import Map Remote
+ + diff --git a/apps/import-map/app2/src/index.ts b/apps/import-map/app2/src/index.ts new file mode 100644 index 00000000000..4944d9a5c95 --- /dev/null +++ b/apps/import-map/app2/src/index.ts @@ -0,0 +1,5 @@ +const root = document.getElementById('root'); + +if (root) { + root.textContent = 'Import Map Remote is running.'; +} diff --git a/apps/import-map/app2/tsconfig.app.json b/apps/import-map/app2/tsconfig.app.json new file mode 100644 index 00000000000..d5657e0790f --- /dev/null +++ b/apps/import-map/app2/tsconfig.app.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.js", + "**/*.test.js", + "dist/**" + ], + "include": ["**/*.ts"] +} diff --git a/apps/import-map/app2/tsconfig.json b/apps/import-map/app2/tsconfig.json new file mode 100644 index 00000000000..4ee1ce40929 --- /dev/null +++ b/apps/import-map/app2/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "./", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/apps/import-map/app2/webpack.config.js b/apps/import-map/app2/webpack.config.js new file mode 100644 index 00000000000..dae88cd3278 --- /dev/null +++ b/apps/import-map/app2/webpack.config.js @@ -0,0 +1,44 @@ +const { composePlugins, withNx } = require('@nx/webpack'); +const { + ModuleFederationPlugin, +} = require('@module-federation/enhanced/webpack'); + +module.exports = composePlugins(withNx(), (config) => { + if (!config.devServer) { + config.devServer = {}; + } + config.devServer.headers = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', + 'Access-Control-Allow-Headers': + 'X-Requested-With, content-type, Authorization', + }; + config.devServer.allowedHosts = 'all'; + + config.plugins.push( + new ModuleFederationPlugin({ + name: 'import_map_remote', + filename: 'remoteEntry.js', + library: { type: 'module' }, + exposes: { + './hello': './src/hello.ts', + }, + }), + ); + + config.devtool = false; + config.experiments = { ...(config.experiments || {}), outputModule: true }; + config.output = { + ...config.output, + module: true, + scriptType: 'module', + publicPath: 'http://127.0.0.1:3102/', + }; + config.optimization = { + ...config.optimization, + runtimeChunk: false, + splitChunks: false, + }; + + return config; +}); From ce45596e89d996b76c6d1df7e478d13b747f49dc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 00:22:38 +0000 Subject: [PATCH 08/16] feat: use script-based build for import map apps --- apps/import-map/README.md | 9 +- apps/import-map/app1/package.json | 7 ++ apps/import-map/app1/project.json | 130 ------------------------- apps/import-map/app1/webpack.config.js | 59 ++++++++--- apps/import-map/app2/package.json | 5 + apps/import-map/app2/project.json | 71 -------------- apps/import-map/app2/webpack.config.js | 77 +++++++++------ apps/import-map/serve-static.js | 64 ++++++++++++ 8 files changed, 174 insertions(+), 248 deletions(-) delete mode 100644 apps/import-map/app1/project.json delete mode 100644 apps/import-map/app2/project.json create mode 100644 apps/import-map/serve-static.js diff --git a/apps/import-map/README.md b/apps/import-map/README.md index f38a765d9d6..cd4f72e9e65 100644 --- a/apps/import-map/README.md +++ b/apps/import-map/README.md @@ -9,8 +9,11 @@ mapped in the import map. Start both apps in separate terminals: ```bash -pnpm nx run import-map-app2:serve -pnpm nx run import-map-app1:serve +pnpm --filter import-map-app2 run build +pnpm --filter import-map-app2 run serve + +pnpm --filter import-map-app1 run build +pnpm --filter import-map-app1 run serve ``` - Host: http://127.0.0.1:3101 @@ -19,5 +22,5 @@ pnpm nx run import-map-app1:serve ## E2E ```bash -pnpm nx run import-map-app1:test:e2e +pnpm --filter import-map-app1 run test:e2e ``` diff --git a/apps/import-map/app1/package.json b/apps/import-map/app1/package.json index 3f70b2341a1..e054d46f7f6 100644 --- a/apps/import-map/app1/package.json +++ b/apps/import-map/app1/package.json @@ -2,6 +2,13 @@ "name": "import-map-app1", "private": true, "version": "0.0.0", + "scripts": { + "build": "webpack --config ./webpack.config.js --mode=development", + "build:prod": "webpack --config ./webpack.config.js --mode=production", + "serve": "node ../serve-static.js --root ./dist --port 3101", + "e2e": "cypress run --config-file ./cypress.config.ts --config baseUrl=http://127.0.0.1:3101 --browser chrome", + "test:e2e": "concurrently -k \"pnpm --filter import-map-app2 run build\" \"pnpm --filter import-map-app1 run build\" \"pnpm --filter import-map-app2 run serve\" \"pnpm --filter import-map-app1 run serve\" \"wait-on http://127.0.0.1:3102/remoteEntry.js http://127.0.0.1:3101 && pnpm --filter import-map-app1 run e2e\"" + }, "devDependencies": { "@module-federation/runtime-core": "workspace:*" } diff --git a/apps/import-map/app1/project.json b/apps/import-map/app1/project.json deleted file mode 100644 index 2f92ed51be2..00000000000 --- a/apps/import-map/app1/project.json +++ /dev/null @@ -1,130 +0,0 @@ -{ - "name": "import-map-app1", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/import-map/app1/src", - "projectType": "application", - "tags": [], - "targets": { - "build": { - "executor": "@nx/webpack:webpack", - "outputs": ["{options.outputPath}"], - "defaultConfiguration": "production", - "options": { - "compiler": "tsc", - "outputPath": "apps/import-map/app1/dist", - "index": "apps/import-map/app1/src/index.html", - "baseHref": "/", - "main": "apps/import-map/app1/src/index.ts", - "tsConfig": "apps/import-map/app1/tsconfig.app.json", - "styles": [], - "scripts": [], - "webpackConfig": "apps/import-map/app1/webpack.config.js" - }, - "configurations": { - "development": { - "extractLicenses": false, - "optimization": false, - "sourceMap": true, - "vendorChunk": true - }, - "production": { - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": false, - "vendorChunk": false - } - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] - }, - "serve": { - "executor": "@nx/webpack:dev-server", - "defaultConfiguration": "production", - "options": { - "buildTarget": "import-map-app1:build", - "hmr": true, - "port": 3101 - }, - "configurations": { - "development": { - "buildTarget": "import-map-app1:build:development" - }, - "production": { - "buildTarget": "import-map-app1:build:production", - "hmr": false - } - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] - }, - "serve-static": { - "executor": "@nx/web:file-server", - "defaultConfiguration": "development", - "options": { - "buildTarget": "import-map-app1:build", - "port": 3101 - }, - "configurations": { - "development": { - "buildTarget": "import-map-app1:build:development" - }, - "production": { - "buildTarget": "import-map-app1:build:production" - } - } - }, - "e2e": { - "executor": "@nx/cypress:cypress", - "options": { - "cypressConfig": "apps/import-map/app1/cypress.config.ts", - "testingType": "e2e", - "baseUrl": "http://127.0.0.1:3101", - "browser": "chrome" - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ], - "configurations": { - "development": { - "runnerUi": true, - "browser": "electron", - "exit": false, - "watch": true - } - } - }, - "test:e2e": { - "executor": "nx:run-commands", - "options": { - "parallel": false, - "commands": [ - { - "command": "nx run import-map-app2:build", - "forwardAllArgs": false - }, - { - "command": "nx run import-map-app1:build", - "forwardAllArgs": false - }, - { - "command": "nx run import-map-app2:serve & nx run import-map-app1:serve & sleep 10 && nx run import-map-app1:e2e", - "forwardAllArgs": false - } - ] - } - } - } -} diff --git a/apps/import-map/app1/webpack.config.js b/apps/import-map/app1/webpack.config.js index 59bea61ad62..77c68434643 100644 --- a/apps/import-map/app1/webpack.config.js +++ b/apps/import-map/app1/webpack.config.js @@ -1,19 +1,50 @@ -const { composePlugins, withNx } = require('@nx/webpack'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); -module.exports = composePlugins(withNx(), (config) => { - config.devtool = false; - config.experiments = { ...(config.experiments || {}), outputModule: true }; - config.output = { - ...config.output, +module.exports = { + entry: path.resolve(__dirname, 'src/index.ts'), + devtool: false, + experiments: { + outputModule: true, + }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + publicPath: 'http://127.0.0.1:3101/', module: true, scriptType: 'module', - publicPath: 'http://127.0.0.1:3101/', - }; - config.optimization = { - ...config.optimization, + clean: true, + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: { + loader: 'swc-loader', + options: { + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es2021', + }, + }, + }, + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'src/index.html'), + scriptLoading: 'module', + }), + ], + optimization: { runtimeChunk: false, splitChunks: false, - }; - - return config; -}); + }, +}; diff --git a/apps/import-map/app2/package.json b/apps/import-map/app2/package.json index 9b6440090b7..374938a8c57 100644 --- a/apps/import-map/app2/package.json +++ b/apps/import-map/app2/package.json @@ -2,6 +2,11 @@ "name": "import-map-app2", "private": true, "version": "0.0.0", + "scripts": { + "build": "webpack --config ./webpack.config.js --mode=development", + "build:prod": "webpack --config ./webpack.config.js --mode=production", + "serve": "node ../serve-static.js --root ./dist --port 3102" + }, "devDependencies": { "@module-federation/enhanced": "workspace:*" } diff --git a/apps/import-map/app2/project.json b/apps/import-map/app2/project.json deleted file mode 100644 index 7ac94b700e6..00000000000 --- a/apps/import-map/app2/project.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "import-map-app2", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "apps/import-map/app2/src", - "projectType": "application", - "tags": [], - "targets": { - "build": { - "executor": "@nx/webpack:webpack", - "outputs": ["{options.outputPath}"], - "defaultConfiguration": "production", - "options": { - "compiler": "tsc", - "outputPath": "apps/import-map/app2/dist", - "index": "apps/import-map/app2/src/index.html", - "baseHref": "/", - "main": "apps/import-map/app2/src/index.ts", - "tsConfig": "apps/import-map/app2/tsconfig.app.json", - "styles": [], - "scripts": [], - "webpackConfig": "apps/import-map/app2/webpack.config.js" - }, - "configurations": { - "development": { - "extractLicenses": false, - "optimization": false, - "sourceMap": true, - "vendorChunk": true - }, - "production": { - "optimization": true, - "outputHashing": "all", - "sourceMap": false, - "namedChunks": false, - "extractLicenses": false, - "vendorChunk": false - } - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] - }, - "serve": { - "executor": "@nx/webpack:dev-server", - "defaultConfiguration": "production", - "options": { - "buildTarget": "import-map-app2:build", - "hmr": true, - "port": 3102 - }, - "configurations": { - "development": { - "buildTarget": "import-map-app2:build:development" - }, - "production": { - "buildTarget": "import-map-app2:build:production", - "hmr": false - } - }, - "dependsOn": [ - { - "target": "build", - "dependencies": true - } - ] - } - } -} diff --git a/apps/import-map/app2/webpack.config.js b/apps/import-map/app2/webpack.config.js index dae88cd3278..3fc4742bfbf 100644 --- a/apps/import-map/app2/webpack.config.js +++ b/apps/import-map/app2/webpack.config.js @@ -1,21 +1,46 @@ -const { composePlugins, withNx } = require('@nx/webpack'); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin, } = require('@module-federation/enhanced/webpack'); -module.exports = composePlugins(withNx(), (config) => { - if (!config.devServer) { - config.devServer = {}; - } - config.devServer.headers = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS', - 'Access-Control-Allow-Headers': - 'X-Requested-With, content-type, Authorization', - }; - config.devServer.allowedHosts = 'all'; - - config.plugins.push( +module.exports = { + entry: path.resolve(__dirname, 'src/index.ts'), + devtool: false, + experiments: { + outputModule: true, + }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + publicPath: 'http://127.0.0.1:3102/', + module: true, + scriptType: 'module', + clean: true, + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: { + loader: 'swc-loader', + options: { + jsc: { + parser: { + syntax: 'typescript', + }, + target: 'es2021', + }, + }, + }, + }, + ], + }, + plugins: [ new ModuleFederationPlugin({ name: 'import_map_remote', filename: 'remoteEntry.js', @@ -24,21 +49,13 @@ module.exports = composePlugins(withNx(), (config) => { './hello': './src/hello.ts', }, }), - ); - - config.devtool = false; - config.experiments = { ...(config.experiments || {}), outputModule: true }; - config.output = { - ...config.output, - module: true, - scriptType: 'module', - publicPath: 'http://127.0.0.1:3102/', - }; - config.optimization = { - ...config.optimization, + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, 'src/index.html'), + scriptLoading: 'module', + }), + ], + optimization: { runtimeChunk: false, splitChunks: false, - }; - - return config; -}); + }, +}; diff --git a/apps/import-map/serve-static.js b/apps/import-map/serve-static.js new file mode 100644 index 00000000000..8cd29e3619c --- /dev/null +++ b/apps/import-map/serve-static.js @@ -0,0 +1,64 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const { URL } = require('url'); + +const args = process.argv.slice(2); +const getArg = (name, fallback) => { + const idx = args.indexOf(name); + if (idx === -1 || idx + 1 >= args.length) { + return fallback; + } + return args[idx + 1]; +}; + +const root = getArg('--root', process.cwd()); +const port = Number(getArg('--port', '3000')); + +const mimeTypes = { + '.html': 'text/html; charset=utf-8', + '.js': 'application/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', +}; + +const server = http.createServer((req, res) => { + const requestUrl = new URL(req.url || '/', `http://${req.headers.host}`); + const rawPath = + requestUrl.pathname === '/' ? '/index.html' : requestUrl.pathname; + const safePath = path.normalize(rawPath).replace(/^(\.\.(\/|\\|$))+/, ''); + const filePath = path.join(root, safePath); + + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS'); + res.setHeader( + 'Access-Control-Allow-Headers', + 'X-Requested-With, content-type, Authorization', + ); + + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + fs.readFile(filePath, (err, data) => { + if (err) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath); + res.writeHead(200, { + 'Content-Type': mimeTypes[ext] || 'application/octet-stream', + }); + res.end(data); + }); +}); + +server.listen(port, () => { + // eslint-disable-next-line no-console + console.log(`Serving ${root} at http://127.0.0.1:${port}`); +}); From 10578b3bf1f0ac2213289b0cc58f6aa788850b0c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 01:11:41 +0000 Subject: [PATCH 09/16] chore: drop nx cypress config for import-map app --- apps/import-map/app1/cypress.config.js | 9 +++++++++ apps/import-map/app1/cypress.config.ts | 12 ------------ apps/import-map/app1/cypress/tsconfig.json | 2 +- apps/import-map/app1/package.json | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 apps/import-map/app1/cypress.config.js delete mode 100644 apps/import-map/app1/cypress.config.ts diff --git a/apps/import-map/app1/cypress.config.js b/apps/import-map/app1/cypress.config.js new file mode 100644 index 00000000000..fa2b0d3dbb4 --- /dev/null +++ b/apps/import-map/app1/cypress.config.js @@ -0,0 +1,9 @@ +const { defineConfig } = require('cypress'); + +module.exports = defineConfig({ + e2e: { + specPattern: 'cypress/e2e/**/*.cy.{js,ts,jsx,tsx}', + supportFile: 'cypress/support/e2e.ts', + }, + defaultCommandTimeout: 20000, +}); diff --git a/apps/import-map/app1/cypress.config.ts b/apps/import-map/app1/cypress.config.ts deleted file mode 100644 index ff1f7f806a2..00000000000 --- a/apps/import-map/app1/cypress.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; -import { defineConfig } from 'cypress'; - -export default defineConfig({ - e2e: { - ...nxE2EPreset(__filename, { cypressDir: 'cypress' }), - // Please ensure you use `cy.origin()` when navigating between domains and remove this option. - // See https://docs.cypress.io/app/references/migration-guide#Changes-to-cyorigin - injectDocumentDomain: true, - }, - defaultCommandTimeout: 20000, -}); diff --git a/apps/import-map/app1/cypress/tsconfig.json b/apps/import-map/app1/cypress/tsconfig.json index 534d462fbc1..f0a0fad198f 100644 --- a/apps/import-map/app1/cypress/tsconfig.json +++ b/apps/import-map/app1/cypress/tsconfig.json @@ -10,7 +10,7 @@ "include": [ "**/*.ts", "**/*.js", - "../cypress.config.ts", + "../cypress.config.js", "../**/*.cy.ts", "../**/*.cy.tsx", "../**/*.cy.js", diff --git a/apps/import-map/app1/package.json b/apps/import-map/app1/package.json index e054d46f7f6..af7a3b50cf0 100644 --- a/apps/import-map/app1/package.json +++ b/apps/import-map/app1/package.json @@ -6,7 +6,7 @@ "build": "webpack --config ./webpack.config.js --mode=development", "build:prod": "webpack --config ./webpack.config.js --mode=production", "serve": "node ../serve-static.js --root ./dist --port 3101", - "e2e": "cypress run --config-file ./cypress.config.ts --config baseUrl=http://127.0.0.1:3101 --browser chrome", + "e2e": "cypress run --config-file ./cypress.config.js --config baseUrl=http://127.0.0.1:3101 --browser chrome", "test:e2e": "concurrently -k \"pnpm --filter import-map-app2 run build\" \"pnpm --filter import-map-app1 run build\" \"pnpm --filter import-map-app2 run serve\" \"pnpm --filter import-map-app1 run serve\" \"wait-on http://127.0.0.1:3102/remoteEntry.js http://127.0.0.1:3101 && pnpm --filter import-map-app1 run e2e\"" }, "devDependencies": { From 343be8bd74d2d82515ac6bdd5377680cca1c5d40 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 9 Feb 2026 13:56:15 +0000 Subject: [PATCH 10/16] feat: support shared import specifiers --- .changeset/clean-cobras-rest.md | 3 ++ packages/runtime-core/README.md | 17 +++++++++ .../__tests__/shared-import.spec.ts | 36 +++++++++++++++++++ packages/runtime-core/src/type/config.ts | 2 ++ packages/runtime-core/src/utils/share.ts | 30 +++++++++++++++- 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 packages/runtime-core/__tests__/shared-import.spec.ts diff --git a/.changeset/clean-cobras-rest.md b/.changeset/clean-cobras-rest.md index bc755640408..8e5bcc894e1 100644 --- a/.changeset/clean-cobras-rest.md +++ b/.changeset/clean-cobras-rest.md @@ -9,3 +9,6 @@ Add import map remote entry support in runtime-core with a tree-shakeable `FEDERATION_OPTIMIZE_NO_IMPORTMAP` flag, and expose `disableImportMap` in Module Federation plugin optimization options (defaulting to `true` in build plugins). + +Shared modules now honor the `import` field to load shared dependencies +via dynamic import (including bare specifiers resolved by import maps). diff --git a/packages/runtime-core/README.md b/packages/runtime-core/README.md index 1dd4b1ee334..fca384fbdc5 100644 --- a/packages/runtime-core/README.md +++ b/packages/runtime-core/README.md @@ -32,6 +32,23 @@ This keeps the entry untouched so the browser/SystemJS can resolve it via the im To tree-shake import map support, define `FEDERATION_OPTIMIZE_NO_IMPORTMAP` as `true` (or use `experiments.optimization.disableImportMap` when using the ModuleFederationPlugin). Note that the build plugins default `disableImportMap` to `true`, so set it to `false` if you want import map support enabled. +### Shared modules with import maps + +You can also load shared modules from import-map specifiers by using the same `import` field naming convention: + +```ts +const mf = new ModuleFederation({ + name: 'host', + shared: { + react: { + version: '18.3.1', + import: 'react', + shareConfig: { singleton: true }, + }, + }, +}); +``` + ## License `@module-federation/runtime` is [MIT licensed](https://github.com/module-federation/core/blob/main/packages/runtime/LICENSE). diff --git a/packages/runtime-core/__tests__/shared-import.spec.ts b/packages/runtime-core/__tests__/shared-import.spec.ts new file mode 100644 index 00000000000..2add2a14dba --- /dev/null +++ b/packages/runtime-core/__tests__/shared-import.spec.ts @@ -0,0 +1,36 @@ +import { assert, describe, expect, it } from 'vitest'; +import { ModuleFederation } from '../src/index'; + +describe('shared import map support', () => { + it('loads shared module from import specifier', async () => { + const moduleSource = + "export default { message: 'Hello from import map shared module' };"; + const specifier = `data:text/javascript,${encodeURIComponent( + moduleSource, + )}`; + + const federation = new ModuleFederation({ + name: '@import-map/shared-host', + remotes: [], + shared: { + 'import-map-shared': { + version: '1.0.0', + import: specifier, + shareConfig: { + singleton: true, + requiredVersion: '^1.0.0', + eager: false, + }, + }, + }, + }); + + const factory = await federation.loadShare<{ + default: { message: string }; + }>('import-map-shared'); + + assert(factory); + const mod = factory(); + expect(mod.default.message).toBe('Hello from import map shared module'); + }); +}); diff --git a/packages/runtime-core/src/type/config.ts b/packages/runtime-core/src/type/config.ts index 4b78a030e39..e6198a40470 100644 --- a/packages/runtime-core/src/type/config.ts +++ b/packages/runtime-core/src/type/config.ts @@ -78,6 +78,7 @@ type SharedBaseArgs = { strategy?: 'version-first' | 'loaded-first'; loaded?: boolean; treeShaking?: TreeShakingArgs; + import?: string | false; }; export type SharedGetter = (() => () => Module) | (() => Promise<() => Module>); @@ -100,6 +101,7 @@ export type Shared = { lib?: () => Module; loaded?: boolean; loading?: null | Promise; + import?: string | false; // compatibility with previous shared eager?: boolean; /** diff --git a/packages/runtime-core/src/utils/share.ts b/packages/runtime-core/src/utils/share.ts index b9a8dfa3ff4..e84e7d21e53 100644 --- a/packages/runtime-core/src/utils/share.ts +++ b/packages/runtime-core/src/utils/share.ts @@ -1,5 +1,5 @@ import { DEFAULT_SCOPE } from '../constant'; -import { TreeShakingStatus } from '@module-federation/sdk'; +import { Module, TreeShakingStatus } from '@module-federation/sdk'; import { Global, Federation } from '../global'; import { GlobalShareScopeMap, @@ -19,6 +19,28 @@ import { satisfy } from './semver'; import { SyncWaterfallHook } from './hooks'; import { addUniqueItem, arrayOptions } from './tool'; +declare const FEDERATION_OPTIMIZE_NO_IMPORTMAP: boolean; +const USE_IMPORTMAP = + typeof FEDERATION_OPTIMIZE_NO_IMPORTMAP === 'boolean' + ? !FEDERATION_OPTIMIZE_NO_IMPORTMAP + : true; + +const createImportGetter = (specifier: string): SharedGetter => { + const dynamicImport = (target: string) => { + if (typeof FEDERATION_ALLOW_NEW_FUNCTION !== 'undefined') { + return new Function('specifier', 'return import(specifier)')(target); + } + return import( + /* webpackIgnore: true */ + /* @vite-ignore */ + target + ); + }; + + return () => + dynamicImport(specifier).then((module) => () => module as Module); +}; + function formatShare( shareArgs: ShareArgs, from: string, @@ -31,6 +53,12 @@ function formatShare( get = shareArgs.get; } else if ('lib' in shareArgs) { get = () => Promise.resolve(shareArgs.lib); + } else if ( + USE_IMPORTMAP && + 'import' in shareArgs && + typeof shareArgs.import === 'string' + ) { + get = createImportGetter(shareArgs.import); } else { get = () => Promise.resolve(() => { From bbc68a270a652ab5871ea80fd9da0e873fdc77ff Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Mon, 9 Feb 2026 06:28:23 -0800 Subject: [PATCH 11/16] fix(runtime-core): avoid implicit any in importmap getter Explicitly type the dynamic import helper to keep the Rollup TS build strict. Co-authored-by: Cursor --- packages/runtime-core/src/utils/share.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/utils/share.ts b/packages/runtime-core/src/utils/share.ts index e84e7d21e53..6d99c758719 100644 --- a/packages/runtime-core/src/utils/share.ts +++ b/packages/runtime-core/src/utils/share.ts @@ -26,15 +26,17 @@ const USE_IMPORTMAP = : true; const createImportGetter = (specifier: string): SharedGetter => { - const dynamicImport = (target: string) => { + const dynamicImport = (target: string): Promise => { if (typeof FEDERATION_ALLOW_NEW_FUNCTION !== 'undefined') { - return new Function('specifier', 'return import(specifier)')(target); + return new Function('specifier', 'return import(specifier)')( + target, + ) as Promise; } return import( /* webpackIgnore: true */ /* @vite-ignore */ target - ); + ) as Promise; }; return () => From ad9a9f4bf7aaa712854df70c85bd249eb1447c34 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 11 Feb 2026 18:59:39 -0800 Subject: [PATCH 12/16] chore(core): add changeset coverage for pr #4390 --- .changeset/auto-pr-4390-coverage.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/auto-pr-4390-coverage.md diff --git a/.changeset/auto-pr-4390-coverage.md b/.changeset/auto-pr-4390-coverage.md new file mode 100644 index 00000000000..12dcdd1d515 --- /dev/null +++ b/.changeset/auto-pr-4390-coverage.md @@ -0,0 +1,8 @@ +--- +'@module-federation/enhanced': patch +'@module-federation/rspack': patch +'@module-federation/runtime-core': patch +'@module-federation/sdk': patch +--- + +Add contextual changeset coverage for packages modified in PR #4390. From 02b682f77bbbf2d301199ad400b9ca4d8170e6fa Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Wed, 11 Feb 2026 19:11:39 -0800 Subject: [PATCH 13/16] chore(runtime-core): remove redundant auto changeset --- .changeset/auto-pr-4390-coverage.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .changeset/auto-pr-4390-coverage.md diff --git a/.changeset/auto-pr-4390-coverage.md b/.changeset/auto-pr-4390-coverage.md deleted file mode 100644 index 12dcdd1d515..00000000000 --- a/.changeset/auto-pr-4390-coverage.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@module-federation/enhanced': patch -'@module-federation/rspack': patch -'@module-federation/runtime-core': patch -'@module-federation/sdk': patch ---- - -Add contextual changeset coverage for packages modified in PR #4390. From 324c01b273dd829367404f3d692d0777be462708 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 24 Feb 2026 15:11:44 -0800 Subject: [PATCH 14/16] fix(dts-plugin): align workspace entrypoints and RawSource typing Resolve the dts-plugin TYPE-001 failure by correcting package entry paths for workspace dependencies and updating RawSource usage for webpack typings. Co-authored-by: Cursor --- .../dts-plugin/src/plugins/GenerateTypesPlugin.ts | 6 ++---- packages/error-codes/package.json | 8 ++++---- packages/sdk/package.json | 12 ++++++------ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts index a8b4aaeed5f..d103436ff60 100644 --- a/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts +++ b/packages/dts-plugin/src/plugins/GenerateTypesPlugin.ts @@ -172,8 +172,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { compilation.emitAsset( zipName, new compiler.webpack.sources.RawSource( - fs.readFileSync(zipTypesPath), - false, + fs.readFileSync(zipTypesPath) as unknown as string, ), ); } @@ -186,8 +185,7 @@ export class GenerateTypesPlugin implements WebpackPluginInstance { compilation.emitAsset( apiFileName, new compiler.webpack.sources.RawSource( - fs.readFileSync(apiTypesPath), - false, + fs.readFileSync(apiTypesPath) as unknown as string, ), ); } diff --git a/packages/error-codes/package.json b/packages/error-codes/package.json index 126b794b07c..352010e14a9 100644 --- a/packages/error-codes/package.json +++ b/packages/error-codes/package.json @@ -25,14 +25,14 @@ "browser": { "url": false }, - "main": "./dist/index.cjs.js", - "module": "./dist/index.esm.mjs", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.esm.mjs", - "require": "./dist/index.cjs.js" + "import": "./dist/index.mjs", + "require": "./dist/index.cjs" } }, "typesVersions": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index cc3f5bde8d4..85026bc4549 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -23,8 +23,8 @@ }, "author": "zhanghang ", "sideEffects": false, - "main": "./dist/index.cjs.cjs", - "module": "./dist/index.esm.js", + "main": "./dist/index.cjs", + "module": "./dist/index.js", "types": "./dist/index.d.ts", "browser": { "url": false @@ -33,21 +33,21 @@ ".": { "import": { "types": "./dist/index.d.ts", - "default": "./dist/index.esm.js" + "default": "./dist/index.js" }, "require": { "types": "./dist/index.d.ts", - "default": "./dist/index.cjs.cjs" + "default": "./dist/index.cjs" } }, "./normalize-webpack-path": { "import": { "types": "./dist/normalize-webpack-path.d.ts", - "default": "./dist/normalize-webpack-path.esm.js" + "default": "./dist/normalize-webpack-path.js" }, "require": { "types": "./dist/normalize-webpack-path.d.ts", - "default": "./dist/normalize-webpack-path.cjs.cjs" + "default": "./dist/normalize-webpack-path.cjs" } } }, From 7fea2f4fa71d1c812740dc298d291d1a0ca6149f Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 24 Feb 2026 19:35:37 -0800 Subject: [PATCH 15/16] fix(bundle-size): restore valid webpack config closure Remove leftover dead config mutation block and close the exported config function so nx format:check can parse the file in CI. Co-authored-by: Cursor --- apps/bundle-size/webpack.config.js | 72 +----------------------------- 1 file changed, 1 insertion(+), 71 deletions(-) diff --git a/apps/bundle-size/webpack.config.js b/apps/bundle-size/webpack.config.js index a2beb441c38..a24ae83a69c 100644 --- a/apps/bundle-size/webpack.config.js +++ b/apps/bundle-size/webpack.config.js @@ -146,74 +146,4 @@ module.exports = (_env, argv = {}) => { minimize: true, }, }; - - // const ModuleFederationPlugin = webpack.container.ModuleFederationPlugin; - config.plugins.push( - new ModuleFederationPlugin({ - name: 'bundle_size', - experiments: { - externalRuntime: false, - asyncStartup: true, - optimization: { - disableSnapshot: true, - disableImportMap: true, - target: 'web', - }, - }, - remotes: {}, - // library: { type: 'var', name: 'runtime_remote' }, - filename: 'remoteEntry.js', - exposes: { - './HelloWorld': './src/HelloWorld.tsx', - }, - dts: { - tsConfigPath: path.resolve(__dirname, 'tsconfig.app.json'), - }, - shareStrategy: 'loaded-first', - shared: { - react: { - singleton: true, - requiredVersion: '^18.2.0', - }, - 'react/': { - singleton: true, - requiredVersion: '^18.2.0', - }, - 'react-dom': { - singleton: true, - requiredVersion: '^18.2.0', - }, - 'react-dom/': { - singleton: true, - requiredVersion: '^18.2.0', - }, - }, - }), - ); - if (!config.devServer) { - config.devServer = {}; - } - config.devServer.host = '127.0.0.1'; - config.plugins.forEach((p) => { - if (p.constructor.name === 'ModuleFederationPlugin') { - //Temporary workaround - https://github.com/nrwl/nx/issues/16983 - p._options.library = undefined; - } - }); - - //Temporary workaround - https://github.com/nrwl/nx/issues/16983 - config.experiments = { outputModule: false }; - - // Update the webpack config as needed here. - // e.g. `config.plugins.push(new MyPlugin())` - config.output = { - ...config.output, - scriptType: 'text/javascript', - }; - config.optimization.runtimeChunk = false; - config.optimization.innerGraph = true; - config.optimization.minimize = true; - // config.optimization.moduleIds = 'named' - // const mf = await withModuleFederation(defaultConfig); - return config; -}); +}; From 833278238da741be66b15219d96c66c4aab8a8cf Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Tue, 24 Feb 2026 19:15:58 -0800 Subject: [PATCH 16/16] fix(sdk): align package entrypoints with emitted artifacts Restore sdk and error-codes export paths to the filenames emitted by the current build so CI package resolution no longer fails on these branches. Co-authored-by: Cursor --- packages/error-codes/package.json | 8 ++++---- packages/sdk/package.json | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/error-codes/package.json b/packages/error-codes/package.json index 352010e14a9..126b794b07c 100644 --- a/packages/error-codes/package.json +++ b/packages/error-codes/package.json @@ -25,14 +25,14 @@ "browser": { "url": false }, - "main": "./dist/index.cjs", - "module": "./dist/index.mjs", + "main": "./dist/index.cjs.js", + "module": "./dist/index.esm.mjs", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.mjs", - "require": "./dist/index.cjs" + "import": "./dist/index.esm.mjs", + "require": "./dist/index.cjs.js" } }, "typesVersions": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 85026bc4549..cc3f5bde8d4 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -23,8 +23,8 @@ }, "author": "zhanghang ", "sideEffects": false, - "main": "./dist/index.cjs", - "module": "./dist/index.js", + "main": "./dist/index.cjs.cjs", + "module": "./dist/index.esm.js", "types": "./dist/index.d.ts", "browser": { "url": false @@ -33,21 +33,21 @@ ".": { "import": { "types": "./dist/index.d.ts", - "default": "./dist/index.js" + "default": "./dist/index.esm.js" }, "require": { "types": "./dist/index.d.ts", - "default": "./dist/index.cjs" + "default": "./dist/index.cjs.cjs" } }, "./normalize-webpack-path": { "import": { "types": "./dist/normalize-webpack-path.d.ts", - "default": "./dist/normalize-webpack-path.js" + "default": "./dist/normalize-webpack-path.esm.js" }, "require": { "types": "./dist/normalize-webpack-path.d.ts", - "default": "./dist/normalize-webpack-path.cjs" + "default": "./dist/normalize-webpack-path.cjs.cjs" } } },