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 93ccfb897bc32..c2e62d1cda4d2 100644 --- a/docs/generated/packages/angular/executors/module-federation-dev-server.json +++ b/docs/generated/packages/angular/executors/module-federation-dev-server.json @@ -122,6 +122,10 @@ "description": "Whether the host that is running this executor is the first in the project tree to do so.", "default": true, "x-priority": "internal" + }, + "parallel": { + "type": "number", + "description": "Max number of parallel processes for building static remotes" } }, "additionalProperties": false, diff --git a/docs/generated/packages/react/executors/module-federation-dev-server.json b/docs/generated/packages/react/executors/module-federation-dev-server.json index 226b164965b32..ce16bdf58d8d5 100644 --- a/docs/generated/packages/react/executors/module-federation-dev-server.json +++ b/docs/generated/packages/react/executors/module-federation-dev-server.json @@ -100,6 +100,10 @@ "description": "Whether the host that is running this executor is the first in the project tree to do so.", "default": true, "x-priority": "internal" + }, + "parallel": { + "type": "number", + "description": "Max number of parallel processes for building static remotes" } }, "presets": [] 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 d923b39c89085..25219523786ae 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 @@ -121,26 +121,20 @@ export function executeModuleFederationDevServerBuilder( pathToManifestFile ); - let isCollectingStaticRemoteOutput = true; - - for (const app of remotes.staticRemotes) { - const remoteProjectServeTarget = - projectGraph.nodes[app].data.targets['serve-static']; - const isUsingModuleFederationDevServerExecutor = - remoteProjectServeTarget.executor.includes( - 'module-federation-dev-server' - ); - let outWithErr: null | string[] = []; + const staticRemoteBuildPromise = new Promise((res) => { + logger.info( + `NX Building ${remotes.staticRemotes.length} static remotes...` + ); const staticProcess = fork( nxBin, [ - 'run', - `${app}:serve-static${ - context.target.configuration ? `:${context.target.configuration}` : '' - }`, - ...(isUsingModuleFederationDevServerExecutor - ? [`--isInitialHost=false`] + 'run-many', + `--target=build`, + `--projects=${remotes.staticRemotes.join(',')}`, + ...(context.target.configuration + ? [`--configuration=${context.target.configuration}`] : []), + ...(options.parallel ? [`--parallel=${options.parallel}`] : []), ], { cwd: context.workspaceRoot, @@ -148,79 +142,131 @@ export function executeModuleFederationDevServerBuilder( } ); staticProcess.stdout.on('data', (data) => { - if (isCollectingStaticRemoteOutput) { - outWithErr.push(data.toString()); - } else { - outWithErr = null; + const ANSII_CODE_REGEX = + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; + const stdoutString = data.toString().replace(ANSII_CODE_REGEX, ''); + if (stdoutString.includes('Successfully ran target build')) { staticProcess.stdout.removeAllListeners('data'); + logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`); + res(); } }); staticProcess.stderr.on('data', (data) => logger.info(data.toString())); staticProcess.on('exit', (code) => { if (code !== 0) { - logger.info(outWithErr.join('')); - throw new Error(`Remote failed to start. See above for errors.`); + throw new Error(`Remotes failed to build. See above for errors.`); } }); process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); process.on('exit', () => staticProcess.kill('SIGTERM')); - } + }); - const devRemotes$ = []; - for (const app of remotes.devRemotes) { - if (!workspaceProjects[app].targets?.['serve']) { - throw new Error(`Could not find "serve" target in "${app}" project.`); - } else if (!workspaceProjects[app].targets?.['serve'].executor) { - throw new Error( - `Could not find executor for "serve" target in "${app}" project.` - ); - } + return from(staticRemoteBuildPromise).pipe( + concatMap(() => { + let isCollectingStaticRemoteOutput = true; - const runOptions: { verbose?: boolean; isInitialHost?: boolean } = {}; - const [collection, executor] = - workspaceProjects[app].targets['serve'].executor.split(':'); - const isUsingModuleFederationDevServerExecutor = executor.includes( - 'module-federation-dev-server' - ); - const { schema } = getExecutorInformation( - collection, - executor, - workspaceRoot - ); - if ( - (options.verbose && schema.additionalProperties) || - 'verbose' in schema.properties - ) { - runOptions.verbose = options.verbose; - } + for (const app of remotes.staticRemotes) { + const remoteProjectServeTarget = + projectGraph.nodes[app].data.targets['serve-static']; + const isUsingModuleFederationDevServerExecutor = + remoteProjectServeTarget.executor.includes( + 'module-federation-dev-server' + ); + let outWithErr: null | string[] = []; + const staticProcess = fork( + nxBin, + [ + 'run', + `${app}:serve-static${ + context.target.configuration + ? `:${context.target.configuration}` + : '' + }`, + ...(isUsingModuleFederationDevServerExecutor + ? [`--isInitialHost=false`] + : []), + ], + { + cwd: context.workspaceRoot, + stdio: ['ignore', 'pipe', 'pipe', 'ipc'], + } + ); + staticProcess.stdout.on('data', (data) => { + if (isCollectingStaticRemoteOutput) { + outWithErr.push(data.toString()); + } else { + outWithErr = null; + staticProcess.stdout.removeAllListeners('data'); + } + }); + staticProcess.stderr.on('data', (data) => logger.info(data.toString())); + staticProcess.on('exit', (code) => { + if (code !== 0) { + logger.info(outWithErr.join('')); + throw new Error(`Remote failed to start. See above for errors.`); + } + }); + process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); + process.on('exit', () => staticProcess.kill('SIGTERM')); + } - if (isUsingModuleFederationDevServerExecutor) { - runOptions.isInitialHost = false; - } + const devRemotes$ = []; + for (const app of remotes.devRemotes) { + if (!workspaceProjects[app].targets?.['serve']) { + throw new Error(`Could not find "serve" target in "${app}" project.`); + } else if (!workspaceProjects[app].targets?.['serve'].executor) { + throw new Error( + `Could not find executor for "serve" target in "${app}" project.` + ); + } - const serve$ = scheduleTarget( - context.workspaceRoot, - { - project: app, - target: 'serve', - configuration: context.target.configuration, - runOptions, - }, - options.verbose - ).then((obs) => { - obs.toPromise().catch((err) => { - throw new Error( - `Remote '${app}' failed to serve correctly due to the following: \r\n${err.toString()}` + const runOptions: { verbose?: boolean; isInitialHost?: boolean } = {}; + const [collection, executor] = + workspaceProjects[app].targets['serve'].executor.split(':'); + const isUsingModuleFederationDevServerExecutor = executor.includes( + 'module-federation-dev-server' ); - }); - }); + const { schema } = getExecutorInformation( + collection, + executor, + workspaceRoot + ); + if ( + (options.verbose && schema.additionalProperties) || + 'verbose' in schema.properties + ) { + runOptions.verbose = options.verbose; + } - devRemotes$.push(serve$); - } + if (isUsingModuleFederationDevServerExecutor) { + runOptions.isInitialHost = false; + } + + const serve$ = scheduleTarget( + context.workspaceRoot, + { + project: app, + target: 'serve', + configuration: context.target.configuration, + runOptions, + }, + options.verbose + ).then((obs) => { + obs.toPromise().catch((err) => { + throw new Error( + `Remote '${app}' failed to serve correctly due to the following: \r\n${err.toString()}` + ); + }); + }); - return devRemotes$.length > 0 - ? combineLatest([...devRemotes$]).pipe(concatMap(() => currExecutor)) - : currExecutor; + devRemotes$.push(serve$); + } + + return devRemotes$.length > 0 + ? combineLatest([...devRemotes$]).pipe(concatMap(() => currExecutor)) + : currExecutor; + }) + ); } export default require('@angular-devkit/architect').createBuilder( 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 e99e9ffb80971..fc23f6e8f62af 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 @@ -22,4 +22,5 @@ export interface Schema { pathToManifestFile?: string; static?: boolean; isInitialHost?: boolean; + parallel?: number; } 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 fec21cb4d3b88..4628a901125d4 100644 --- a/packages/angular/src/builders/module-federation-dev-server/schema.json +++ b/packages/angular/src/builders/module-federation-dev-server/schema.json @@ -132,6 +132,10 @@ "description": "Whether the host that is running this executor is the first in the project tree to do so.", "default": true, "x-priority": "internal" + }, + "parallel": { + "type": "number", + "description": "Max number of parallel processes for building static remotes" } }, "additionalProperties": false, diff --git a/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts index b8c7ee75e2985..7f8967e18d07a 100644 --- a/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -24,6 +24,7 @@ type ModuleFederationDevServerOptions = WebDevServerOptions & { skipRemotes?: string[]; static?: boolean; isInitialHost?: boolean; + parallel?: number; }; function getBuildOptions(buildTarget: string, context: ExecutorContext) { @@ -80,6 +81,44 @@ export default async function* moduleFederationDevServer( } ); + logger.info(`NX Building ${remotes.staticRemotes.length} static remotes...`); + await new Promise((res) => { + const staticProcess = fork( + nxBin, + [ + 'run-many', + `--target=build`, + `--projects=${remotes.staticRemotes.join(',')}`, + ...(context.configurationName + ? [`--configuration=${context.configurationName}`] + : []), + ...(options.parallel ? [`--parallel=${options.parallel}`] : []), + ], + { + cwd: context.root, + stdio: ['ignore', 'pipe', 'pipe', 'ipc'], + } + ); + staticProcess.stdout.on('data', (data) => { + const ANSII_CODE_REGEX = + /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; + const stdoutString = data.toString().replace(ANSII_CODE_REGEX, ''); + if (stdoutString.includes('Successfully ran target build')) { + staticProcess.stdout.removeAllListeners('data'); + logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`); + res(); + } + }); + staticProcess.stderr.on('data', (data) => logger.info(data.toString())); + staticProcess.on('exit', (code) => { + if (code !== 0) { + throw new Error(`Remote failed to start. See above for errors.`); + } + }); + process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); + process.on('exit', () => staticProcess.kill('SIGTERM')); + }); + let isCollectingStaticRemoteOutput = true; const devRemoteIters: AsyncIterable<{ success: boolean }>[] = []; diff --git a/packages/react/src/executors/module-federation-dev-server/schema.json b/packages/react/src/executors/module-federation-dev-server/schema.json index 9ae43269b1d46..b8e3ceeccd18f 100644 --- a/packages/react/src/executors/module-federation-dev-server/schema.json +++ b/packages/react/src/executors/module-federation-dev-server/schema.json @@ -101,6 +101,10 @@ "description": "Whether the host that is running this executor is the first in the project tree to do so.", "default": true, "x-priority": "internal" + }, + "parallel": { + "type": "number", + "description": "Max number of parallel processes for building static remotes" } } }