diff --git a/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap b/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap index 36940b5a55263..d3d1633134cf6 100644 --- a/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap +++ b/packages/angular/src/utils/mfe/__snapshots__/with-module-federation.spec.ts.snap @@ -23,6 +23,11 @@ Array [ "eager": undefined, "requiredVersion": false, }, + "lodash": Object { + "requiredVersion": undefined, + "singleton": true, + "strictVersion": true, + }, "shared": Object { "eager": undefined, "requiredVersion": false, diff --git a/packages/angular/src/utils/mfe/with-module-federation.spec.ts b/packages/angular/src/utils/mfe/with-module-federation.spec.ts index d88a9b8d4eb55..567d061aab572 100644 --- a/packages/angular/src/utils/mfe/with-module-federation.spec.ts +++ b/packages/angular/src/utils/mfe/with-module-federation.spec.ts @@ -140,7 +140,7 @@ describe('withModuleFederation', () => { { target: 'npm:zone.js' }, { target: 'core' }, ], - core: [{ target: 'shared' }], + core: [{ target: 'shared' }, { target: 'npm:lodash' }], }, }); diff --git a/packages/angular/src/utils/mfe/with-module-federation.ts b/packages/angular/src/utils/mfe/with-module-federation.ts index c89e7076c2c5f..015689fd438ea 100644 --- a/packages/angular/src/utils/mfe/with-module-federation.ts +++ b/packages/angular/src/utils/mfe/with-module-federation.ts @@ -30,9 +30,15 @@ export interface MFEConfig { ) => SharedLibraryConfig | false; } +interface DependencySets { + workspaceLibraries: Set; + npmPackages: Set; +} + function recursivelyResolveWorkspaceDependents( projectGraph: ProjectGraph, target: string, + dependencySets: DependencySets, seenTargets: Set = new Set() ) { if (seenTargets.has(target)) { @@ -43,7 +49,19 @@ function recursivelyResolveWorkspaceDependents( const workspaceDependencies = ( projectGraph.dependencies[target] ?? [] - ).filter((dep) => !dep.target.startsWith('npm:')); + ).filter((dep) => { + const isNpm = dep.target.startsWith('npm:'); + + // If this is a npm dep ensure it is going to be added as a dep of this MFE so it can be shared if needed + if (isNpm) { + dependencySets.npmPackages.add(dep.target.replace('npm:', '')); + } else { + dependencySets.workspaceLibraries.add(dep.target); + } + + return !isNpm; + }); + if (workspaceDependencies.length > 0) { for (const dep of workspaceDependencies) { dependencies = [ @@ -51,6 +69,7 @@ function recursivelyResolveWorkspaceDependents( ...recursivelyResolveWorkspaceDependents( projectGraph, dep.target, + dependencySets, seenTargets ), ]; @@ -103,7 +122,8 @@ async function getDependentPackagesForProject(name: string) { projectGraph = await createProjectGraphAsync(); } - const deps = projectGraph.dependencies[name].reduce( + // Build Sets for the direct deps (internal and external) for this MFE app + const dependencySets = projectGraph.dependencies[name].reduce( (dependencies, dependency) => { const workspaceLibraries = new Set(dependencies.workspaceLibraries); const npmPackages = new Set(dependencies.npmPackages); @@ -115,24 +135,27 @@ async function getDependentPackagesForProject(name: string) { } return { - workspaceLibraries: [...workspaceLibraries], - npmPackages: [...npmPackages], + workspaceLibraries, + npmPackages, }; }, - { workspaceLibraries: [], npmPackages: [] } + { workspaceLibraries: new Set(), npmPackages: new Set() } ); + const seenWorkspaceLibraries = new Set(); - deps.workspaceLibraries = deps.workspaceLibraries.reduce( - (workspaceLibraryDeps, workspaceLibrary) => [ - ...workspaceLibraryDeps, - ...recursivelyResolveWorkspaceDependents( - projectGraph, - workspaceLibrary, - seenWorkspaceLibraries - ), - ], - [] - ); + dependencySets.workspaceLibraries.forEach((workspaceLibrary) => { + recursivelyResolveWorkspaceDependents( + projectGraph, + workspaceLibrary, + dependencySets, + seenWorkspaceLibraries + ); + }); + + const deps = { + workspaceLibraries: [...dependencySets.workspaceLibraries], + npmPackages: [...dependencySets.npmPackages], + }; deps.workspaceLibraries = mapWorkspaceLibrariesToTsConfigImport( deps.workspaceLibraries diff --git a/packages/react/src/module-federation/with-module-federation.ts b/packages/react/src/module-federation/with-module-federation.ts index bd0145f1b04be..aae3ccc6043e0 100644 --- a/packages/react/src/module-federation/with-module-federation.ts +++ b/packages/react/src/module-federation/with-module-federation.ts @@ -15,9 +15,15 @@ import { readWorkspaceJson } from 'nx/src/project-graph/file-utils'; import { ModuleFederationConfig, Remotes } from './models'; import ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); +interface DependencySets { + workspaceLibraries: Set; + npmPackages: Set; +} + function recursivelyResolveWorkspaceDependents( projectGraph: ProjectGraph, target: string, + dependencySets: DependencySets, seenTargets: Set = new Set() ) { if (seenTargets.has(target)) { @@ -28,7 +34,19 @@ function recursivelyResolveWorkspaceDependents( const workspaceDependencies = ( projectGraph.dependencies[target] ?? [] - ).filter((dep) => !dep.target.startsWith('npm:')); + ).filter((dep) => { + const isNpm = dep.target.startsWith('npm:'); + + // If this is a npm dep ensure it is going to be added as a dep of this MFE so it can be shared if needed + if (isNpm) { + dependencySets.npmPackages.add(dep.target.replace('npm:', '')); + } else { + dependencySets.workspaceLibraries.add(dep.target); + } + + return !isNpm; + }); + if (workspaceDependencies.length > 0) { for (const dep of workspaceDependencies) { dependencies = [ @@ -36,6 +54,7 @@ function recursivelyResolveWorkspaceDependents( ...recursivelyResolveWorkspaceDependents( projectGraph, dep.target, + dependencySets, seenTargets ), ]; @@ -81,15 +100,15 @@ function mapWorkspaceLibrariesToTsConfigImport(workspaceLibraries: string[]) { } async function getDependentPackagesForProject(name: string) { - let projectGraph: ProjectGraph; - + let projectGraph: ProjectGraph; try { projectGraph = readCachedProjectGraph(); } catch (e) { projectGraph = await createProjectGraphAsync(); } - const deps = projectGraph.dependencies[name].reduce( + // Build Sets for the direct deps (internal and external) for this MFE app + const dependencySets = projectGraph.dependencies[name].reduce( (dependencies, dependency) => { const workspaceLibraries = new Set(dependencies.workspaceLibraries); const npmPackages = new Set(dependencies.npmPackages); @@ -101,24 +120,27 @@ async function getDependentPackagesForProject(name: string) { } return { - workspaceLibraries: [...workspaceLibraries], - npmPackages: [...npmPackages], + workspaceLibraries, + npmPackages, }; }, - { workspaceLibraries: [], npmPackages: [] } + { workspaceLibraries: new Set(), npmPackages: new Set() } ); + const seenWorkspaceLibraries = new Set(); - deps.workspaceLibraries = deps.workspaceLibraries.reduce( - (workspaceLibraryDeps, workspaceLibrary) => [ - ...workspaceLibraryDeps, - ...recursivelyResolveWorkspaceDependents( - projectGraph, - workspaceLibrary, - seenWorkspaceLibraries - ), - ], - [] - ); + dependencySets.workspaceLibraries.forEach((workspaceLibrary) => { + recursivelyResolveWorkspaceDependents( + projectGraph, + workspaceLibrary, + dependencySets, + seenWorkspaceLibraries + ); + }); + + const deps = { + workspaceLibraries: [...dependencySets.workspaceLibraries], + npmPackages: [...dependencySets.npmPackages], + }; deps.workspaceLibraries = mapWorkspaceLibrariesToTsConfigImport( deps.workspaceLibraries