diff --git a/docs/generated/packages/angular/executors/module-federation-dev-server.json b/docs/generated/packages/angular/executors/module-federation-dev-server.json index 571d98fb25..18fb24395c 100644 --- a/docs/generated/packages/angular/executors/module-federation-dev-server.json +++ b/docs/generated/packages/angular/executors/module-federation-dev-server.json @@ -108,6 +108,10 @@ "type": "array", "items": { "type": "string" }, "description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository." + }, + "pathToManifestFile": { + "type": "string", + "description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root." } }, "additionalProperties": false, diff --git a/docs/generated/packages/angular/executors/module-federation-dev-ssr.json b/docs/generated/packages/angular/executors/module-federation-dev-ssr.json index 973528cd8e..9c4cb1cc1a 100644 --- a/docs/generated/packages/angular/executors/module-federation-dev-ssr.json +++ b/docs/generated/packages/angular/executors/module-federation-dev-ssr.json @@ -77,6 +77,10 @@ "verbose": { "type": "boolean", "description": "Adds more details to output logging." + }, + "pathToManifestFile": { + "type": "string", + "description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root." } }, "additionalProperties": false, diff --git a/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts index 6d85996c79..a79457ab64 100644 --- a/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -12,6 +12,8 @@ import { getStaticRemotes, validateDevRemotes, } from '../utilities/module-federation'; +import { existsSync } from 'fs'; +import { extname, join } from 'path'; export function executeModuleFederationDevServerBuilder( schema: Schema, @@ -24,6 +26,29 @@ export function executeModuleFederationDevServerBuilder( const ws = new Workspaces(workspaceRoot); const project = workspaceProjects[context.target.project]; + let pathToManifestFile = join( + context.workspaceRoot, + project.sourceRoot, + 'assets/module-federation.manifest.json' + ); + if (options.pathToManifestFile) { + const userPathToManifestFile = join( + context.workspaceRoot, + options.pathToManifestFile + ); + if (!existsSync(userPathToManifestFile)) { + throw new Error( + `The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".` + ); + } else if (extname(options.pathToManifestFile) !== '.json') { + throw new Error( + `The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.` + ); + } + + pathToManifestFile = userPathToManifestFile; + } + validateDevRemotes(options, workspaceProjects); const remotesToSkip = new Set(options.skipRemotes ?? []); @@ -37,7 +62,8 @@ export function executeModuleFederationDevServerBuilder( project, context, workspaceProjects, - remotesToSkip + remotesToSkip, + pathToManifestFile ); const remotes = [...staticRemotes, ...dynamicRemotes]; diff --git a/packages/angular/src/builders/module-federation-dev-server/schema.d.ts b/packages/angular/src/builders/module-federation-dev-server/schema.d.ts index 2d5cc3ca52..cf3f1ebc52 100644 --- a/packages/angular/src/builders/module-federation-dev-server/schema.d.ts +++ b/packages/angular/src/builders/module-federation-dev-server/schema.d.ts @@ -19,4 +19,5 @@ export interface Schema { poll?: number; devRemotes?: string[]; skipRemotes?: string[]; + pathToManifestFile?: string; } diff --git a/packages/angular/src/builders/module-federation-dev-server/schema.json b/packages/angular/src/builders/module-federation-dev-server/schema.json index d2480b000e..008903e882 100644 --- a/packages/angular/src/builders/module-federation-dev-server/schema.json +++ b/packages/angular/src/builders/module-federation-dev-server/schema.json @@ -118,6 +118,10 @@ "type": "string" }, "description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository." + }, + "pathToManifestFile": { + "type": "string", + "description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root." } }, "additionalProperties": false, diff --git a/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts b/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts index 8f440dfda3..6db5b9416c 100644 --- a/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts +++ b/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts @@ -12,9 +12,10 @@ import { } from '../utilities/module-federation'; import { switchMap, tap } from 'rxjs/operators'; import { from } from 'rxjs'; -import { join } from 'path'; +import { extname, join } from 'path'; import { execSync, fork } from 'child_process'; import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter'; +import { existsSync } from 'fs'; export function executeModuleFederationDevSSRBuilder( schema: Schema, @@ -27,6 +28,29 @@ export function executeModuleFederationDevSSRBuilder( const ws = new Workspaces(workspaceRoot); const project = workspaceProjects[context.target.project]; + let pathToManifestFile = join( + context.workspaceRoot, + project.sourceRoot, + 'assets/module-federation.manifest.json' + ); + if (options.pathToManifestFile) { + const userPathToManifestFile = join( + context.workspaceRoot, + options.pathToManifestFile + ); + if (!existsSync(userPathToManifestFile)) { + throw new Error( + `The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".` + ); + } else if (extname(options.pathToManifestFile) !== '.json') { + throw new Error( + `The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.` + ); + } + + pathToManifestFile = userPathToManifestFile; + } + validateDevRemotes(options, workspaceProjects); const remotesToSkip = new Set(options.skipRemotes ?? []); @@ -40,7 +64,8 @@ export function executeModuleFederationDevSSRBuilder( project, context, workspaceProjects, - remotesToSkip + remotesToSkip, + pathToManifestFile ); const remotes = [...staticRemotes, ...dynamicRemotes]; diff --git a/packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts b/packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts index 7c3697f41f..9e64faa6bd 100644 --- a/packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts +++ b/packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts @@ -13,4 +13,5 @@ export interface Schema { devRemotes?: string[]; skipRemotes?: string[]; verbose?: boolean; + pathToManifestFile?: string; } diff --git a/packages/angular/src/builders/module-federation-dev-ssr/schema.json b/packages/angular/src/builders/module-federation-dev-ssr/schema.json index 0b2a7787d9..f724526d46 100644 --- a/packages/angular/src/builders/module-federation-dev-ssr/schema.json +++ b/packages/angular/src/builders/module-federation-dev-ssr/schema.json @@ -78,6 +78,10 @@ "verbose": { "type": "boolean", "description": "Adds more details to output logging." + }, + "pathToManifestFile": { + "type": "string", + "description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root." } }, "additionalProperties": false, diff --git a/packages/angular/src/builders/utilities/module-federation.ts b/packages/angular/src/builders/utilities/module-federation.ts index cc1b58b18c..4a9eebf09e 100644 --- a/packages/angular/src/builders/utilities/module-federation.ts +++ b/packages/angular/src/builders/utilities/module-federation.ts @@ -7,23 +7,23 @@ export function getDynamicRemotes( project: ProjectConfiguration, context: import('@angular-devkit/architect').BuilderContext, workspaceProjects: Record, - remotesToSkip: Set + remotesToSkip: Set, + pathToManifestFile = join( + context.workspaceRoot, + project.sourceRoot, + 'assets/module-federation.manifest.json' + ) ): string[] { // check for dynamic remotes // we should only check for dynamic based on what we generate // and fallback to empty array - const standardPathToGeneratedMFManifestJson = join( - context.workspaceRoot, - project.sourceRoot, - 'assets/module-federation.manifest.json' - ); - if (!existsSync(standardPathToGeneratedMFManifestJson)) { + if (!existsSync(pathToManifestFile)) { return []; } const moduleFederationManifestJson = readFileSync( - standardPathToGeneratedMFManifestJson, + pathToManifestFile, 'utf-8' ); @@ -54,8 +54,8 @@ export function getDynamicRemotes( if (invalidDynamicRemotes.length) { throw new Error( invalidDynamicRemotes.length === 1 - ? `Invalid dynamic remote configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes[0]}.` - : `Invalid dynamic remotes configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes.join( + ? `Invalid dynamic remote configured in "${pathToManifestFile}": ${invalidDynamicRemotes[0]}.` + : `Invalid dynamic remotes configured in "${pathToManifestFile}": ${invalidDynamicRemotes.join( ', ' )}.` );