diff --git a/.changeset/mf-shared-array-deep-imports.md b/.changeset/mf-shared-array-deep-imports.md new file mode 100644 index 000000000..dfdd4a2da --- /dev/null +++ b/.changeset/mf-shared-array-deep-imports.md @@ -0,0 +1,5 @@ +--- +"@callstack/repack": patch +--- + +Read the `react-native` config from the array form of `shared` in `ModuleFederationPlugin`, so the generated `react-native/` and `@react-native/` deep imports inherit its `eager`, `import` and `version` values instead of falling back to the defaults. diff --git a/packages/repack/src/plugins/ModuleFederationPluginV1.ts b/packages/repack/src/plugins/ModuleFederationPluginV1.ts index 81a570d7c..0fc240b6a 100644 --- a/packages/repack/src/plugins/ModuleFederationPluginV1.ts +++ b/packages/repack/src/plugins/ModuleFederationPluginV1.ts @@ -205,9 +205,16 @@ export class ModuleFederationPluginV1 { dependencies: SharedDependencies ): SharedConfig | string | undefined => { if (Array.isArray(dependencies)) { - return dependencies.find((item) => - typeof item === 'string' ? item === name : Boolean(item[name]) - ); + // object entries wrap the config under the dependency name, + // so unwrap it instead of returning the wrapper + for (const item of dependencies) { + if (typeof item === 'string') { + if (item === name) return item; + } else if (item[name]) { + return item[name]; + } + } + return undefined; } return dependencies[name]; }; diff --git a/packages/repack/src/plugins/ModuleFederationPluginV2.ts b/packages/repack/src/plugins/ModuleFederationPluginV2.ts index 210a207eb..ddb329a10 100644 --- a/packages/repack/src/plugins/ModuleFederationPluginV2.ts +++ b/packages/repack/src/plugins/ModuleFederationPluginV2.ts @@ -220,9 +220,16 @@ export class ModuleFederationPluginV2 { dependencies: MF.Shared ): MF.SharedConfig | string | undefined => { if (Array.isArray(dependencies)) { - return dependencies.find((item) => - typeof item === 'string' ? item === name : Boolean(item[name]) - ); + // object entries wrap the config under the dependency name, + // so unwrap it instead of returning the wrapper + for (const item of dependencies) { + if (typeof item === 'string') { + if (item === name) return item; + } else if (item[name]) { + return item[name]; + } + } + return undefined; } return dependencies[name]; }; diff --git a/packages/repack/src/plugins/__tests__/ModuleFederationPluginV1.test.ts b/packages/repack/src/plugins/__tests__/ModuleFederationPluginV1.test.ts index 75756c792..18eacfb36 100644 --- a/packages/repack/src/plugins/__tests__/ModuleFederationPluginV1.test.ts +++ b/packages/repack/src/plugins/__tests__/ModuleFederationPluginV1.test.ts @@ -227,6 +227,39 @@ describe('ModuleFederationPlugin', () => { expect(config.shared['@react-native/'].import).toBe(false); }); + it('should determine eager based on shared react-native config in array', () => { + new ModuleFederationPluginV1({ + name: 'test', + shared: [ + { react: { singleton: true, eager: true } }, + { + 'react-native': { + singleton: true, + eager: false, + requiredVersion: '0.76.0', + }, + }, + ], + }).apply(mockCompiler); + + const config = mockPlugin.mock.calls[0][0]; + expect(config.shared[2]['react-native/'].eager).toBe(false); + expect(config.shared[3]['@react-native/'].eager).toBe(false); + expect(config.shared[2]['react-native/'].requiredVersion).toBe('0.76.0'); + expect(config.shared[3]['@react-native/'].requiredVersion).toBe('0.76.0'); + }); + + it('should propagate import=false to deep imports in array', () => { + new ModuleFederationPluginV1({ + name: 'test', + shared: [{ 'react-native': { singleton: true, import: false } }], + }).apply(mockCompiler); + + const config = mockPlugin.mock.calls[0][0]; + expect(config.shared[1]['react-native/'].import).toBe(false); + expect(config.shared[2]['@react-native/'].import).toBe(false); + }); + it('should set default federated entry filename', () => { new ModuleFederationPluginV1({ name: 'test', diff --git a/packages/repack/src/plugins/__tests__/ModuleFederationPluginV2.test.ts b/packages/repack/src/plugins/__tests__/ModuleFederationPluginV2.test.ts index e512e0377..4071c87c9 100644 --- a/packages/repack/src/plugins/__tests__/ModuleFederationPluginV2.test.ts +++ b/packages/repack/src/plugins/__tests__/ModuleFederationPluginV2.test.ts @@ -166,6 +166,31 @@ describe('ModuleFederationPlugin', () => { expect(config.shared['@react-native/'].import).toBe(false); }); + it('should determine eager based on shared react-native config in array', () => { + new ModuleFederationPluginV2({ + name: 'test', + shared: [ + { react: { singleton: true, eager: true } }, + { 'react-native': { singleton: true, eager: false } }, + ], + }).apply(mockCompiler); + + const config = mockPlugin.mock.calls[0][0]; + expect(config.shared[2]['react-native/'].eager).toBe(false); + expect(config.shared[3]['@react-native/'].eager).toBe(false); + }); + + it('should propagate import=false to deep imports in array', () => { + new ModuleFederationPluginV2({ + name: 'test', + shared: [{ 'react-native': { singleton: true, import: false } }], + }).apply(mockCompiler); + + const config = mockPlugin.mock.calls[0][0]; + expect(config.shared[1]['react-native/'].import).toBe(false); + expect(config.shared[2]['@react-native/'].import).toBe(false); + }); + it('should add CorePlugin & ResolverPlugin to runtime plugins by default', () => { new ModuleFederationPluginV2({ name: 'test' }).apply(mockCompiler);