From 91a2dfc0aeb88ef9fe10eb6d98168298cc720a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leosvel=20P=C3=A9rez=20Espinosa?= Date: Mon, 8 Apr 2024 14:39:07 +0200 Subject: [PATCH] fix(angular): fix dynamic module federation generation (#22724) (cherry picked from commit 18a2878de616aba9993f6fa4c3d8ef476caf4cd4) --- .../__snapshots__/setup-mf.spec.ts.snap | 34 +++++++++ .../module-federation.config.ts__tmpl__ | 2 +- .../module-federation.config.js__tmpl__ | 2 +- .../setup-mf/lib/add-remote-to-host.ts | 34 ++++----- .../setup-mf/lib/generate-config.ts | 1 + .../setup-mf/lib/update-host-app-routes.ts | 13 +--- .../src/generators/setup-mf/setup-mf.spec.ts | 70 +++++++++++++++++++ .../src/generators/setup-mf/setup-mf.ts | 15 +++- 8 files changed, 135 insertions(+), 36 deletions(-) diff --git a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap index ea405b975b001..9feb7270fd29c 100644 --- a/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap +++ b/packages/angular/src/generators/setup-mf/__snapshots__/setup-mf.spec.ts.snap @@ -18,6 +18,40 @@ fetch('/assets/module-federation.manifest.json') .then(() => import('./bootstrap').catch(err => console.error(err)));" `; +exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly 1`] = ` +"import { NxWelcomeComponent } from './nx-welcome.component'; +import { Route } from '@angular/router'; +import { loadRemoteModule } from '@nx/angular/mf'; + +export const appRoutes: Route[] = [ + { + path: 'remote1', + loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule) + }, + { + path: '', + component: NxWelcomeComponent + },]; +" +`; + +exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true 1`] = ` +"import { NxWelcomeComponent } from './nx-welcome.component'; +import { Route } from '@angular/router'; +import { loadRemoteModule } from '@nx/angular/mf'; + +export const appRoutes: Route[] = [ + { + path: 'remote1', + loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule) + }, + { + path: '', + component: NxWelcomeComponent + },]; +" +`; + exports[`Init MF should add a remote application and add it to a specified host applications router config 1`] = ` "import { NxWelcomeComponent } from './nx-welcome.component'; import { Route } from '@angular/router'; diff --git a/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__ b/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__ index f785a0535c8b3..52d7d6cf9d122 100644 --- a/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__ +++ b/packages/angular/src/generators/setup-mf/files/ts-webpack/module-federation.config.ts__tmpl__ @@ -14,7 +14,7 @@ const config: ModuleFederationConfig = { * declare module 'my-external-remote'; * */ - remotes: [<% remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); %>]<% } %><% if(type === 'remote') { %> + remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %> exposes: {<% if(standalone) { %> './Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %> './Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %> diff --git a/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__ b/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__ index 831bed36aa64f..d2014b48c86e7 100644 --- a/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__ +++ b/packages/angular/src/generators/setup-mf/files/webpack/module-federation.config.js__tmpl__ @@ -12,7 +12,7 @@ module.exports = { * declare module 'my-external-remote'; * */ - remotes: [<% remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); %>]<% } %><% if(type === 'remote') { %> + remotes: [<% if (federationType === 'static') { remotes.forEach(function(remote) { %>'<%= remote.remoteName %>',<% }); } %>]<% } %><% if(type === 'remote') { %> exposes: {<% if(standalone) { %> './Routes': '<%= projectRoot %>/src/app/remote-entry/entry.routes.ts',<% } else { %> './Module': '<%= projectRoot %>/src/app/remote-entry/entry.module.ts',<% } %> diff --git a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts index 4880fb46585d8..ffb94f3806d12 100644 --- a/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts +++ b/packages/angular/src/generators/setup-mf/lib/add-remote-to-host.ts @@ -6,7 +6,6 @@ import { Tree, updateJson, } from '@nx/devkit'; -import type { Schema } from '../schema'; import { ArrayLiteralExpression } from 'typescript'; import { insertImport } from '@nx/js'; import { addRoute } from '../../../utils/nx-devkit/route-utils'; @@ -14,6 +13,13 @@ import { ensureTypescript } from '@nx/js/src/utils/typescript/ensure-typescript' let tsModule: typeof import('typescript'); +export type AddRemoteOptions = { + host: string; + appName: string; + standalone: boolean; + port: number; +}; + export function checkIsCommaNeeded(mfRemoteText: string) { const remoteText = mfRemoteText.replace(/\s+/g, ''); return !remoteText.endsWith(',]') @@ -23,7 +29,7 @@ export function checkIsCommaNeeded(mfRemoteText: string) { : false; } -export function addRemoteToHost(tree: Tree, options: Schema) { +export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) { if (options.host) { const hostProject = readProjectConfiguration(tree, options.host); const pathToMFManifest = joinPathFragments( @@ -50,20 +56,6 @@ export function addRemoteToHost(tree: Tree, options: Schema) { addRemoteToDynamicHost(tree, options, pathToMFManifest); } - // const declarationFilePath = joinPathFragments( - // hostProject.sourceRoot, - // 'remotes.d.ts' - // ); - // - // const declarationFileContent = - // (tree.exists(declarationFilePath) - // ? tree.read(declarationFilePath, 'utf-8') - // : '') + - // `\ndeclare module '${options.appName}/${ - // options.standalone ? `Routes` : `Module` - // }';`; - // tree.write(declarationFilePath, declarationFileContent); - addLazyLoadedRouteToHostAppModule(tree, options, hostFederationType); } } @@ -77,13 +69,13 @@ function determineHostFederationType( function addRemoteToStaticHost( tree: Tree, - options: Schema, + options: AddRemoteOptions, hostProject: ProjectConfiguration, - isHostUsingTypescrpt: boolean + isHostUsingTypescript: boolean ) { const hostMFConfigPath = joinPathFragments( hostProject.root, - isHostUsingTypescrpt + isHostUsingTypescript ? 'module-federation.config.ts' : 'module-federation.config.js' ); @@ -115,7 +107,7 @@ function addRemoteToStaticHost( function addRemoteToDynamicHost( tree: Tree, - options: Schema, + options: AddRemoteOptions, pathToMfManifest: string ) { updateJson(tree, pathToMfManifest, (manifest) => { @@ -129,7 +121,7 @@ function addRemoteToDynamicHost( // TODO(colum): future work: allow dev to pass to path to routing module function addLazyLoadedRouteToHostAppModule( tree: Tree, - options: Schema, + options: AddRemoteOptions, hostFederationType: 'dynamic' | 'static' ) { if (!tsModule) { diff --git a/packages/angular/src/generators/setup-mf/lib/generate-config.ts b/packages/angular/src/generators/setup-mf/lib/generate-config.ts index fb70976446ef0..4f9a6f518feb1 100644 --- a/packages/angular/src/generators/setup-mf/lib/generate-config.ts +++ b/packages/angular/src/generators/setup-mf/lib/generate-config.ts @@ -33,6 +33,7 @@ export function generateWebpackConfig( { tmpl: '', type: options.mfType, + federationType: options.federationType, name: options.appName, remotes: remotesWithPorts ?? [], projectRoot: appRoot, diff --git a/packages/angular/src/generators/setup-mf/lib/update-host-app-routes.ts b/packages/angular/src/generators/setup-mf/lib/update-host-app-routes.ts index 0931fb164f3da..caa38b2d28d94 100644 --- a/packages/angular/src/generators/setup-mf/lib/update-host-app-routes.ts +++ b/packages/angular/src/generators/setup-mf/lib/update-host-app-routes.ts @@ -14,21 +14,10 @@ export function updateHostAppRoutes(tree: Tree, options: Schema) { const { sourceRoot } = readProjectConfiguration(tree, options.appName); - const remoteRoutes = - options.remotes && Array.isArray(options.remotes) - ? options.remotes.reduce( - (routes, remote) => - `${routes}\n
  • ${ - names(remote).className - }
  • `, - '' - ) - : ''; - tree.write( joinPathFragments(sourceRoot, 'app/app.component.html'), ` ` diff --git a/packages/angular/src/generators/setup-mf/setup-mf.spec.ts b/packages/angular/src/generators/setup-mf/setup-mf.spec.ts index 42ebf6217178a..9d7d24ef8f6da 100644 --- a/packages/angular/src/generators/setup-mf/setup-mf.spec.ts +++ b/packages/angular/src/generators/setup-mf/setup-mf.spec.ts @@ -535,6 +535,76 @@ describe('Init MF', () => { ).toBeTruthy(); expect(tree.read('app1/src/main.ts', 'utf-8')).toMatchSnapshot(); }); + + it('should wire up existing remote to dynamic host correctly', async () => { + await setupMf(tree, { + appName: 'remote1', + mfType: 'remote', + port: 4201, + routing: true, + typescriptConfiguration: false, + standalone: false, + skipFormat: true, + }); + + await setupMf(tree, { + appName: 'app1', + mfType: 'host', + routing: true, + federationType: 'dynamic', + remotes: ['remote1'], + typescriptConfiguration: false, + standalone: false, + skipFormat: true, + }); + + expect(tree.read('app1/module-federation.config.js', 'utf-8')).toContain( + 'remotes: []' + ); + expect( + readJson(tree, 'app1/src/assets/module-federation.manifest.json') + ).toEqual({ + remote1: 'http://localhost:4201', + }); + expect( + tree.read('app1/src/app/app.routes.ts', 'utf-8') + ).toMatchSnapshot(); + }); + + it('should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true', async () => { + await setupMf(tree, { + appName: 'remote1', + mfType: 'remote', + port: 4201, + routing: true, + typescriptConfiguration: true, + standalone: false, + skipFormat: true, + }); + + await setupMf(tree, { + appName: 'app1', + mfType: 'host', + routing: true, + federationType: 'dynamic', + remotes: ['remote1'], + typescriptConfiguration: true, + standalone: false, + skipFormat: true, + }); + + expect(tree.read('app1/module-federation.config.ts', 'utf-8')).toContain( + 'remotes: []' + ); + expect( + readJson(tree, 'app1/src/assets/module-federation.manifest.json') + ).toEqual({ + remote1: 'http://localhost:4201', + }); + expect( + tree.read('app1/src/app/app.routes.ts', 'utf-8') + ).toMatchSnapshot(); + }); }); it('should add a remote to dynamic host correctly', async () => { diff --git a/packages/angular/src/generators/setup-mf/setup-mf.ts b/packages/angular/src/generators/setup-mf/setup-mf.ts index c4ad3cd12f0b4..5d79e4450fe98 100644 --- a/packages/angular/src/generators/setup-mf/setup-mf.ts +++ b/packages/angular/src/generators/setup-mf/setup-mf.ts @@ -30,7 +30,12 @@ export async function setupMf(tree: Tree, rawOptions: Schema) { let installTask = () => {}; if (options.mfType === 'remote') { - addRemoteToHost(tree, options); + addRemoteToHost(tree, { + appName: options.appName, + host: options.host, + standalone: options.standalone, + port: options.port, + }); addRemoteEntry(tree, options, projectConfig.root); removeDeadCodeFromRemote(tree, options); setupTspathForRemote(tree, options); @@ -54,6 +59,14 @@ export async function setupMf(tree: Tree, rawOptions: Schema) { if (options.mfType === 'host') { setupHostIfDynamic(tree, options); updateHostAppRoutes(tree, options); + for (const { remoteName, port } of remotesWithPorts) { + addRemoteToHost(tree, { + appName: remoteName, + host: options.appName, + standalone: options.standalone, + port, + }); + } installTask = addDependenciesToPackageJson( tree, {},