From 5448046f06006d02d2a1e6e9459ecbe6f6ae8635 Mon Sep 17 00:00:00 2001 From: Colum Ferry Date: Tue, 3 Dec 2024 13:15:20 +0000 Subject: [PATCH] feat(module-federation): move common executor logic to module-federation package (#29151) ## Current Behavior The logic for the `module-federation-dev-server` and `module-federation-ssr-dev-server` is duplicated across Angular, React and Rspack. The majority of this logic is the same, and the duplication causes an increased maintenance tax. ## Expected Behavior Move the logic into a utility that is exposed from `@nx/module-federation`. --- CODEOWNERS | 1 - .../lib/build-static-remotes.ts | 91 ----- .../module-federation-dev-server/lib/index.ts | 2 - .../lib/normalize-options.ts | 1 + .../lib/start-dev-remotes.ts | 2 +- .../module-federation-dev-server.impl.ts | 94 +---- .../module-federation-dev-server/schema.d.ts | 1 + .../lib/build-static-remotes.ts | 91 ----- .../lib/normalize-options.ts | 5 +- .../lib/start-dev-remotes.ts | 2 +- .../lib/start-static-remotes.ts | 54 --- .../module-federation-ssr-dev-server.impl.ts | 114 ++----- .../schema.d.ts | 6 + packages/module-federation/package.json | 1 + .../executors/utils/build-static-remotes.ts} | 14 +- .../src/executors/utils/index.ts | 4 + .../src/executors/utils/models.ts | 38 +++ .../executors/utils/start-remote-iterators.ts | 123 +++++++ .../start-static-remotes-file-server.ts | 43 ++- .../module-federation-dev-server/lib/index.ts | 2 + .../lib/normalize-options.ts | 29 ++ .../lib/start-remotes.ts | 62 ++++ .../module-federation-dev-server.impl.ts | 228 +------------ .../module-federation-dev-server/schema.d.ts | 16 +- .../lib/index.ts | 2 + .../lib/normalize-options.ts | 34 ++ .../lib/start-remotes.ts | 58 ++++ .../module-federation-ssr-dev-server.impl.ts | 323 +----------------- .../schema.d.ts | 29 ++ .../module-federation-static-server.impl.ts | 2 +- .../module-federation-dev-server/lib/index.ts | 2 + .../lib/normalize-options.ts | 29 ++ .../lib/start-remotes.ts | 64 ++++ .../module-federation-dev-server.impl.ts | 228 +------------ .../module-federation-dev-server/schema.d.ts | 7 + .../lib/index.ts | 2 + .../lib/normalize-options.ts | 34 ++ .../lib/start-remotes.ts | 58 ++++ .../module-federation-ssr-dev-server.impl.ts | 323 +----------------- .../schema.d.ts | 29 ++ .../module-federation-static-server.impl.ts | 2 +- .../module-federation/build-static.remotes.ts | 97 ------ 42 files changed, 761 insertions(+), 1586 deletions(-) delete mode 100644 packages/angular/src/executors/module-federation-dev-server/lib/build-static-remotes.ts delete mode 100644 packages/angular/src/executors/module-federation-ssr-dev-server/lib/build-static-remotes.ts delete mode 100644 packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-static-remotes.ts rename packages/{react/src/utils/build-static.remotes.ts => module-federation/src/executors/utils/build-static-remotes.ts} (87%) create mode 100644 packages/module-federation/src/executors/utils/index.ts create mode 100644 packages/module-federation/src/executors/utils/models.ts create mode 100644 packages/module-federation/src/executors/utils/start-remote-iterators.ts rename packages/{angular/src/executors/module-federation-dev-server/lib => module-federation/src/executors/utils}/start-static-remotes-file-server.ts (56%) create mode 100644 packages/react/src/executors/module-federation-dev-server/lib/index.ts create mode 100644 packages/react/src/executors/module-federation-dev-server/lib/normalize-options.ts create mode 100644 packages/react/src/executors/module-federation-dev-server/lib/start-remotes.ts create mode 100644 packages/react/src/executors/module-federation-ssr-dev-server/lib/index.ts create mode 100644 packages/react/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts create mode 100644 packages/react/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts create mode 100644 packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts create mode 100644 packages/rspack/src/executors/module-federation-dev-server/lib/index.ts create mode 100644 packages/rspack/src/executors/module-federation-dev-server/lib/normalize-options.ts create mode 100644 packages/rspack/src/executors/module-federation-dev-server/lib/start-remotes.ts create mode 100644 packages/rspack/src/executors/module-federation-ssr-dev-server/lib/index.ts create mode 100644 packages/rspack/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts create mode 100644 packages/rspack/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts create mode 100644 packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts delete mode 100644 packages/rspack/src/utils/module-federation/build-static.remotes.ts diff --git a/CODEOWNERS b/CODEOWNERS index b983bc355a0df..7419f6615225c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -96,7 +96,6 @@ rust-toolchain @nrwl/nx-native-reviewers /packages/webpack/** @nrwl/nx-js-reviewers /e2e/webpack/** @nrwl/nx-js-reviewers /packages/rspack/** @nrwl/nx-js-reviewers -/packages/rspack/src/utils/module-federation @nrwl/nx-js-reviewers /e2e/rspack/** @nrwl/nx-js-reviewers /packages/esbuild/** @nrwl/nx-js-reviewers /e2e/esbuild/** @nrwl/nx-js-reviewers diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/build-static-remotes.ts b/packages/angular/src/executors/module-federation-dev-server/lib/build-static-remotes.ts deleted file mode 100644 index 54acba1ea8455..0000000000000 --- a/packages/angular/src/executors/module-federation-dev-server/lib/build-static-remotes.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { type Schema } from '../schema'; -import { type ExecutorContext, logger } from '@nx/devkit'; -import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; -import { fork } from 'node:child_process'; -import { join } from 'node:path'; -import { createWriteStream } from 'node:fs'; -import type { StaticRemotesConfig } from '@nx/module-federation/src/utils'; - -export async function buildStaticRemotes( - staticRemotesConfig: StaticRemotesConfig, - nxBin, - context: ExecutorContext, - options: Schema -) { - if (!staticRemotesConfig.remotes.length) { - return; - } - const mappedLocationOfRemotes: Record = {}; - for (const app of staticRemotesConfig.remotes) { - mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${ - options.host - }:${options.staticRemotesPort}/${ - staticRemotesConfig.config[app].urlSegment - }`; - } - - await new Promise((res, rej) => { - logger.info( - `NX Building ${staticRemotesConfig.remotes.length} static remotes...` - ); - const staticProcess = fork( - nxBin, - [ - 'run-many', - `--target=build`, - `--projects=${staticRemotesConfig.remotes.join(',')}`, - ...(context.configurationName - ? [`--configuration=${context.configurationName}`] - : []), - ...(options.parallel ? [`--parallel=${options.parallel}`] : []), - ], - { - cwd: context.root, - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - } - ); - // File to debug build failures e.g. 2024-01-01T00_00_0_0Z-build.log' - const remoteBuildLogFile = join( - workspaceDataDirectory, - `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` - ); - const stdoutStream = createWriteStream(remoteBuildLogFile); - 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, ''); - stdoutStream.write(stdoutString); - - // in addition to writing into the stdout stream, also show error directly in console - // so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output. - if (stdoutString.includes('ERROR in')) { - logger.log(stdoutString); - } - - if (stdoutString.includes('Successfully ran target build')) { - staticProcess.stdout.removeAllListeners('data'); - logger.info( - `NX Built ${staticRemotesConfig.remotes.length} static remotes` - ); - res(); - } - }); - staticProcess.stderr.on('data', (data) => logger.info(data.toString())); - staticProcess.once('exit', (code) => { - stdoutStream.end(); - staticProcess.stdout.removeAllListeners('data'); - staticProcess.stderr.removeAllListeners('data'); - if (code !== 0) { - rej( - `Remote failed to start. A complete log can be found in: ${remoteBuildLogFile}` - ); - } else { - res(); - } - }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); - process.on('exit', () => staticProcess.kill('SIGTERM')); - }); - - return mappedLocationOfRemotes; -} diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/index.ts b/packages/angular/src/executors/module-federation-dev-server/lib/index.ts index 331719948e2b9..2f41930a016b8 100644 --- a/packages/angular/src/executors/module-federation-dev-server/lib/index.ts +++ b/packages/angular/src/executors/module-federation-dev-server/lib/index.ts @@ -1,4 +1,2 @@ -export * from './build-static-remotes'; export * from './normalize-options'; export * from './start-dev-remotes'; -export * from './start-static-remotes-file-server'; diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts b/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts index 19f46c1ab02a4..41d63136dbfb0 100644 --- a/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts +++ b/packages/angular/src/executors/module-federation-dev-server/lib/normalize-options.ts @@ -26,6 +26,7 @@ export function normalizeOptions(schema: Schema): NormalizedSchema { liveReload: schema.liveReload ?? true, open: schema.open ?? false, ssl: schema.ssl ?? false, + verbose: schema.verbose ?? false, sslCert: schema.sslCert ? join(workspaceRoot, schema.sslCert) : undefined, sslKey: schema.sslKey ? join(workspaceRoot, schema.sslKey) : undefined, }; diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/start-dev-remotes.ts b/packages/angular/src/executors/module-federation-dev-server/lib/start-dev-remotes.ts index 321aa6ed7a27a..58836fab8ee16 100644 --- a/packages/angular/src/executors/module-federation-dev-server/lib/start-dev-remotes.ts +++ b/packages/angular/src/executors/module-federation-dev-server/lib/start-dev-remotes.ts @@ -8,7 +8,7 @@ import { export async function startRemotes( remotes: string[], workspaceProjects: Record, - options: Schema, + options: Pick, context: ExecutorContext, target: 'serve' | 'serve-static' = 'serve' ) { diff --git a/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts index f3d5b7d4a5b34..7575516818cb6 100644 --- a/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -4,24 +4,14 @@ import { readProjectsConfigurationFromProjectGraph, } from '@nx/devkit'; import { type Schema } from './schema'; -import { - buildStaticRemotes, - normalizeOptions, - startRemotes, - startStaticRemotesFileServer, -} from './lib'; +import { normalizeOptions, startRemotes } from './lib'; import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await'; import { combineAsyncIterables, createAsyncIterable, mapAsyncIterable, } from '@nx/devkit/src/utils/async-iterable'; -import { - getModuleFederationConfig, - getRemotes, - startRemoteProxies, - parseStaticRemotesConfig, -} from '@nx/module-federation/src/utils'; +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; @@ -37,8 +27,6 @@ export async function* moduleFederationDevServerExecutor( schema: Schema, context: ExecutorContext ) { - // Force Node to resolve to look for the nx binary that is inside node_modules - const nxBin = require.resolve('nx/bin/nx'); const options = normalizeOptions(schema); const { projects: workspaceProjects } = @@ -101,76 +89,14 @@ export async function* moduleFederationDevServerExecutor( validateDevRemotes(options, workspaceProjects); - const moduleFederationConfig = getModuleFederationConfig( - project.targets.build.options.tsConfig, - context.root, - project.root, - 'angular' - ); - - const remoteNames = options.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: project.name, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - - options.staticRemotesPort ??= remotes.staticRemotePort; - - // Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - remotes.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - project.name.replace(/-/g, '_'), - ]); - - const staticRemotesConfig = parseStaticRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - const mappedLocationsOfStaticRemotes = await buildStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - const devRemoteIters = await startRemotes( - remotes.devRemotes, - workspaceProjects, - options, - context, - 'serve' - ); - - const staticRemotesIter = startStaticRemotesFileServer( - staticRemotesConfig, - context, - options - ); - - startRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { - pathToCert: options.sslCert, - pathToKey: options.sslKey, - } - : undefined - ); + const { remotes, staticRemotesIter, devRemoteIters } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'angular' + ); const removeBaseUrlEmission = (iter: AsyncIterable) => mapAsyncIterable(iter, (v) => ({ diff --git a/packages/angular/src/executors/module-federation-dev-server/schema.d.ts b/packages/angular/src/executors/module-federation-dev-server/schema.d.ts index c2e5f77430021..f8bcc10f31c24 100644 --- a/packages/angular/src/executors/module-federation-dev-server/schema.d.ts +++ b/packages/angular/src/executors/module-federation-dev-server/schema.d.ts @@ -43,4 +43,5 @@ export type NormalizedSchema = SchemaWithBuildTarget & { liveReload: boolean; open: boolean; ssl: boolean; + verbose: boolean; }; diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/build-static-remotes.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/build-static-remotes.ts deleted file mode 100644 index fd026d73b4417..0000000000000 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/build-static-remotes.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Schema } from '../schema'; -import { type ExecutorContext, logger } from '@nx/devkit'; -import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; -import { fork } from 'node:child_process'; -import { join } from 'node:path'; -import { createWriteStream } from 'node:fs'; -import type { StaticRemotesConfig } from '@nx/module-federation/src/utils'; - -export async function buildStaticRemotes( - staticRemotesConfig: StaticRemotesConfig, - nxBin, - context: ExecutorContext, - options: Schema -) { - if (!staticRemotesConfig.remotes.length) { - return; - } - const mappedLocationOfRemotes: Record = {}; - for (const app of staticRemotesConfig.remotes) { - mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${ - options.host - }:${options.staticRemotesPort}/${ - staticRemotesConfig.config[app].urlSegment - }`; - } - - await new Promise((resolve, reject) => { - logger.info( - `NX Building ${staticRemotesConfig.remotes.length} static remotes...` - ); - const staticProcess = fork( - nxBin, - [ - 'run-many', - `--target=server`, - `--projects=${staticRemotesConfig.remotes.join(',')}`, - ...(context.configurationName - ? [`--configuration=${context.configurationName}`] - : []), - ...(options.parallel ? [`--parallel=${options.parallel}`] : []), - ], - { - cwd: context.root, - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - } - ); - // File to debug build failures e.g. 2024-01-01T00_00_0_0Z-build.log' - const remoteBuildLogFile = join( - workspaceDataDirectory, - `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` - ); - const stdoutStream = createWriteStream(remoteBuildLogFile); - 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, ''); - stdoutStream.write(stdoutString); - - // in addition to writing into the stdout stream, also show error directly in console - // so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output. - if (stdoutString.includes('ERROR in')) { - logger.log(stdoutString); - } - - if (stdoutString.includes('Successfully ran target server')) { - staticProcess.stdout.removeAllListeners('data'); - logger.info( - `NX Built ${staticRemotesConfig.remotes.length} static remotes` - ); - resolve(); - } - }); - staticProcess.stderr.on('data', (data) => logger.info(data.toString())); - staticProcess.once('exit', (code) => { - stdoutStream.end(); - staticProcess.stdout.removeAllListeners('data'); - staticProcess.stderr.removeAllListeners('data'); - if (code !== 0) { - reject( - `Remote failed to start. A complete log can be found in: ${remoteBuildLogFile}` - ); - } else { - resolve(); - } - }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); - process.on('exit', () => staticProcess.kill('SIGTERM')); - }); - - return mappedLocationOfRemotes; -} diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts index 70e22c1de1df4..89d4a67204485 100644 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts +++ b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts @@ -1,8 +1,8 @@ import { workspaceRoot } from '@nx/devkit'; -import type { Schema } from '../schema'; +import type { NormalizedSchema, Schema } from '../schema'; import { join } from 'path'; -export function normalizeOptions(options: Schema): Schema { +export function normalizeOptions(options: Schema): NormalizedSchema { const devServeRemotes = !options.devRemotes ? [] : Array.isArray(options.devRemotes) @@ -12,6 +12,7 @@ export function normalizeOptions(options: Schema): Schema { return { ...options, devRemotes: devServeRemotes, + verbose: options.verbose ?? false, ssl: options.ssl ?? false, sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined, sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined, diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-dev-remotes.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-dev-remotes.ts index a935b10473eb4..a0650f5eb357e 100644 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-dev-remotes.ts +++ b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-dev-remotes.ts @@ -8,7 +8,7 @@ import { export async function startRemotes( remotes: string[], workspaceProjects: Record, - options: Schema, + options: Pick, context: ExecutorContext ) { const target = 'serve-ssr'; diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-static-remotes.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-static-remotes.ts deleted file mode 100644 index 0e2d8972d740a..0000000000000 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/lib/start-static-remotes.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { type ExecutorContext, workspaceRoot } from '@nx/devkit'; -import { type Schema } from '../schema'; -import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; -import { join } from 'path'; -import { cpSync, rmSync } from 'fs'; -import type { StaticRemotesConfig } from '@nx/module-federation/src/utils'; -import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable'; - -export function startStaticRemotes( - ssrStaticRemotesConfig: StaticRemotesConfig, - context: ExecutorContext, - options: Schema -) { - if (ssrStaticRemotesConfig.remotes.length === 0) { - return createAsyncIterable(({ next, done }) => { - next({ success: true }); - done(); - }); - } - - // The directories are usually generated with /browser and /server suffixes so we need to copy them to a common directory - const commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes'); - for (const app of ssrStaticRemotesConfig.remotes) { - const remoteConfig = ssrStaticRemotesConfig.config[app]; - - cpSync( - remoteConfig.outputPath, - join(commonOutputDirectory, remoteConfig.urlSegment), - { - force: true, - recursive: true, - } - ); - } - - const staticRemotesIter = fileServerExecutor( - { - cors: true, - watch: false, - staticFilePath: commonOutputDirectory, - parallel: false, - spa: false, - withDeps: false, - host: options.host, - port: options.staticRemotesPort, - ssl: options.ssl, - sslCert: options.sslCert, - sslKey: options.sslKey, - cacheSeconds: -1, - }, - context - ); - return staticRemotesIter; -} diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts index 9810eba64d438..c53565e683605 100644 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts +++ b/packages/angular/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts @@ -1,37 +1,29 @@ import { executeSSRDevServerBuilder } from '@angular-devkit/build-angular'; import { type ExecutorContext, logger } from '@nx/devkit'; -import { - combineAsyncIterables, - createAsyncIterable, - mapAsyncIterable, -} from '@nx/devkit/src/utils/async-iterable'; -import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await'; -import { - getModuleFederationConfig, - getRemotes, - parseStaticSsrRemotesConfig, - startSsrRemoteProxies, -} from '@nx/module-federation/src/utils'; -import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; import { existsSync } from 'fs'; -import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph'; import { extname, join } from 'path'; import { getDynamicMfManifestFile, validateDevRemotes, } from '../../builders/utilities/module-federation'; -import { buildStaticRemotes } from './lib/build-static-remotes'; -import { normalizeOptions } from './lib/normalize-options'; -import { startRemotes } from './lib/start-dev-remotes'; -import { startStaticRemotes } from './lib/start-static-remotes'; import type { Schema } from './schema'; +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; +import { startRemotes } from './lib/start-dev-remotes'; +import { + combineAsyncIterables, + createAsyncIterable, + mapAsyncIterable, +} from '@nx/devkit/src/utils/async-iterable'; +import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await'; +import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter'; +import { normalizeOptions } from './lib/normalize-options'; +import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; export async function* moduleFederationSsrDevServerExecutor( schema: Schema, context: ExecutorContext ) { - const nxBin = require.resolve('nx/bin/nx'); const options = normalizeOptions(schema); const currIter = eachValueFrom( @@ -79,73 +71,15 @@ export async function* moduleFederationSsrDevServerExecutor( validateDevRemotes({ devRemotes: options.devRemotes }, workspaceProjects); - const moduleFederationConfig = getModuleFederationConfig( - project.targets.build.options.tsConfig, - context.root, - project.root, - 'angular' - ); - - const remoteNames = options.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: project.name, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - - options.staticRemotesPort ??= remotes.staticRemotePort; - - const staticRemotesConfig = parseStaticSsrRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - - const mappedLocationsOfStaticRemotes = await buildStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - // Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - options.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - project.name.replace(/-/g, '_'), - ]); - - const devRemotes = await startRemotes( - remotes.devRemotes, - workspaceProjects, - options, - context - ); - - const staticRemotes = startStaticRemotes( - staticRemotesConfig, - context, - options - ); - - startSsrRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { pathToCert: options.sslCert, pathToKey: options.sslKey } - : undefined - ); + const { remotes, staticRemotesIter, devRemoteIters } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'angular', + true + ); const removeBaseUrlEmission = (iter: AsyncIterable) => mapAsyncIterable(iter, (v) => ({ @@ -154,8 +88,8 @@ export async function* moduleFederationSsrDevServerExecutor( })); const combined = combineAsyncIterables( - removeBaseUrlEmission(staticRemotes), - ...(devRemotes ? devRemotes.map(removeBaseUrlEmission) : []), + removeBaseUrlEmission(staticRemotesIter), + ...(devRemoteIters ? devRemoteIters.map(removeBaseUrlEmission) : []), createAsyncIterable<{ success: true; baseUrl: string }>( async ({ next, done }) => { if (!options.isInitialHost) { @@ -172,7 +106,7 @@ export async function* moduleFederationSsrDevServerExecutor( return; } try { - const portsToWaitFor = staticRemotes + const portsToWaitFor = staticRemotesIter ? [options.staticRemotesPort, ...remotes.remotePorts] : [...remotes.remotePorts]; await Promise.all( @@ -198,7 +132,7 @@ export async function* moduleFederationSsrDevServerExecutor( } ) ); - let refs = 2 + (devRemotes?.length ?? 0); + let refs = 2 + (devRemoteIters?.length ?? 0); for await (const result of combined) { if (result.success === false) throw new Error('Remotes failed to start'); if (result.success) refs--; diff --git a/packages/angular/src/executors/module-federation-ssr-dev-server/schema.d.ts b/packages/angular/src/executors/module-federation-ssr-dev-server/schema.d.ts index 4c43a78789aae..821a5834dd974 100644 --- a/packages/angular/src/executors/module-federation-ssr-dev-server/schema.d.ts +++ b/packages/angular/src/executors/module-federation-ssr-dev-server/schema.d.ts @@ -9,3 +9,9 @@ export interface Schema extends SSRDevServerBuilderOptions { staticRemotesPort?: number; isInitialHost?: boolean; } + +export interface NormalizedSchema extends Schema { + devRemotes: DevRemoteDefinition[]; + ssl: boolean; + verbose: boolean; +} diff --git a/packages/module-federation/package.json b/packages/module-federation/package.json index a40eb3618a568..21da73e961858 100644 --- a/packages/module-federation/package.json +++ b/packages/module-federation/package.json @@ -27,6 +27,7 @@ "tslib": "^2.3.0", "@nx/devkit": "file:../devkit", "@nx/js": "file:../js", + "@nx/web": "file:../web", "picocolors": "^1.1.0", "webpack": "5.88.0", "@rspack/core": "1.1.3", diff --git a/packages/react/src/utils/build-static.remotes.ts b/packages/module-federation/src/executors/utils/build-static-remotes.ts similarity index 87% rename from packages/react/src/utils/build-static.remotes.ts rename to packages/module-federation/src/executors/utils/build-static-remotes.ts index 946d65331d8d1..b99d8fd6610c6 100644 --- a/packages/react/src/utils/build-static.remotes.ts +++ b/packages/module-federation/src/executors/utils/build-static-remotes.ts @@ -1,7 +1,6 @@ -import { StaticRemotesConfig } from '@nx/module-federation/src/utils'; -import { ExecutorContext } from '@nx/devkit'; -import { ModuleFederationDevServerOptions } from '../executors/module-federation-dev-server/schema'; -import { logger } from 'nx/src/utils/logger'; +import { ExecutorContext, logger } from '@nx/devkit'; +import { type StaticRemotesConfig } from '../../utils'; +import { type BuildStaticRemotesOptions } from './models'; import { fork } from 'node:child_process'; import { join } from 'path'; import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; @@ -11,7 +10,8 @@ export async function buildStaticRemotes( staticRemotesConfig: StaticRemotesConfig, nxBin, context: ExecutorContext, - options: ModuleFederationDevServerOptions + options: BuildStaticRemotesOptions, + buildTarget: 'build' | 'server' = 'build' ) { if (!staticRemotesConfig.remotes.length) { return; @@ -34,7 +34,7 @@ export async function buildStaticRemotes( nxBin, [ 'run-many', - `--target=build`, + `--target=${buildTarget}`, `--projects=${staticRemotesConfig.remotes.join(',')}`, ...(context.configurationName ? [`--configuration=${context.configurationName}`] @@ -66,7 +66,7 @@ export async function buildStaticRemotes( logger.log(stdoutString); } - if (stdoutString.includes('Successfully ran target build')) { + if (stdoutString.includes(`Successfully ran target ${buildTarget}`)) { staticProcess.stdout.removeAllListeners('data'); logger.info( `NX Built ${staticRemotesConfig.remotes.length} static remotes` diff --git a/packages/module-federation/src/executors/utils/index.ts b/packages/module-federation/src/executors/utils/index.ts new file mode 100644 index 0000000000000..916cecc631e0a --- /dev/null +++ b/packages/module-federation/src/executors/utils/index.ts @@ -0,0 +1,4 @@ +export * from './start-static-remotes-file-server'; +export * from './build-static-remotes'; +export * from './start-remote-iterators'; +export { DevRemoteDefinition } from './models'; diff --git a/packages/module-federation/src/executors/utils/models.ts b/packages/module-federation/src/executors/utils/models.ts new file mode 100644 index 0000000000000..362705319e66f --- /dev/null +++ b/packages/module-federation/src/executors/utils/models.ts @@ -0,0 +1,38 @@ +import type { ProjectConfiguration, ExecutorContext } from '@nx/devkit'; + +export type DevRemoteDefinition = + | string + | { remoteName: string; configuration: string }; + +export type StartRemoteFn = ( + remotes: string[], + workspaceProjects: Record, + options: { + devRemotes: DevRemoteDefinition[]; + verbose: boolean; + }, + context: ExecutorContext, + target: 'serve' | 'serve-static' +) => Promise[]>; + +export interface StaticRemotesOptions { + staticRemotesPort?: number; + host?: string; + ssl?: boolean; + sslCert?: string; + sslKey?: string; +} + +export interface BuildStaticRemotesOptions extends StaticRemotesOptions { + parallel?: number; +} + +export interface StartRemoteIteratorsOptions extends BuildStaticRemotesOptions { + devRemotes: DevRemoteDefinition[]; + skipRemotes?: string[]; + buildTarget?: string; + liveReload?: boolean; + open?: boolean; + ssl?: boolean; + verbose: boolean; +} diff --git a/packages/module-federation/src/executors/utils/start-remote-iterators.ts b/packages/module-federation/src/executors/utils/start-remote-iterators.ts new file mode 100644 index 0000000000000..d21245a4b1eda --- /dev/null +++ b/packages/module-federation/src/executors/utils/start-remote-iterators.ts @@ -0,0 +1,123 @@ +import { StartRemoteFn, type StartRemoteIteratorsOptions } from './models'; +import { + getModuleFederationConfig, + getRemotes, + parseStaticRemotesConfig, + parseStaticSsrRemotesConfig, + startRemoteProxies, + startSsrRemoteProxies, +} from '../../utils'; +import { buildStaticRemotes } from './build-static-remotes'; +import { + startSsrStaticRemotesFileServer, + startStaticRemotesFileServer, +} from './start-static-remotes-file-server'; +import { + type ExecutorContext, + readProjectsConfigurationFromProjectGraph, +} from '@nx/devkit'; + +export async function startRemoteIterators( + options: StartRemoteIteratorsOptions, + context: ExecutorContext, + startRemoteFn: StartRemoteFn, + pathToManifestFile: string | undefined, + pluginName: 'react' | 'angular' = 'react', + isServer = false +) { + const nxBin = require.resolve('nx/bin/nx'); + const { projects: workspaceProjects } = + readProjectsConfigurationFromProjectGraph(context.projectGraph); + const project = workspaceProjects[context.projectName]; + const moduleFederationConfig = getModuleFederationConfig( + project.targets.build.options.tsConfig, + context.root, + project.root, + pluginName + ); + + const remoteNames = options.devRemotes.map((r) => + typeof r === 'string' ? r : r.remoteName + ); + + const remotes = getRemotes( + remoteNames, + options.skipRemotes, + moduleFederationConfig, + { + projectName: project.name, + projectGraph: context.projectGraph, + root: context.root, + }, + pathToManifestFile + ); + + options.staticRemotesPort ??= remotes.staticRemotePort; + + // Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin + process.env.NX_MF_DEV_REMOTES = JSON.stringify([ + ...( + remotes.devRemotes.map((r) => + typeof r === 'string' ? r : r.remoteName + ) ?? [] + ).map((r) => r.replace(/-/g, '_')), + project.name.replace(/-/g, '_'), + ]); + + const staticRemotesConfig = isServer + ? parseStaticSsrRemotesConfig( + [...remotes.staticRemotes, ...remotes.dynamicRemotes], + context + ) + : parseStaticRemotesConfig( + [...remotes.staticRemotes, ...remotes.dynamicRemotes], + context + ); + const mappedLocationsOfStaticRemotes = await buildStaticRemotes( + staticRemotesConfig, + nxBin, + context, + options, + isServer ? 'server' : 'build' + ); + + const devRemoteIters = await startRemoteFn( + remotes.devRemotes, + workspaceProjects, + options, + context, + 'serve' + ); + + const staticRemotesIter = isServer + ? startSsrStaticRemotesFileServer(staticRemotesConfig, context, options) + : startStaticRemotesFileServer(staticRemotesConfig, context, options); + + isServer + ? startSsrRemoteProxies( + staticRemotesConfig, + mappedLocationsOfStaticRemotes, + options.ssl + ? { + pathToCert: options.sslCert, + pathToKey: options.sslKey, + } + : undefined + ) + : startRemoteProxies( + staticRemotesConfig, + mappedLocationsOfStaticRemotes, + options.ssl + ? { + pathToCert: options.sslCert, + pathToKey: options.sslKey, + } + : undefined + ); + + return { + remotes, + devRemoteIters, + staticRemotesIter, + }; +} diff --git a/packages/angular/src/executors/module-federation-dev-server/lib/start-static-remotes-file-server.ts b/packages/module-federation/src/executors/utils/start-static-remotes-file-server.ts similarity index 56% rename from packages/angular/src/executors/module-federation-dev-server/lib/start-static-remotes-file-server.ts rename to packages/module-federation/src/executors/utils/start-static-remotes-file-server.ts index c44fadee06a74..982c1db4acc7f 100644 --- a/packages/angular/src/executors/module-federation-dev-server/lib/start-static-remotes-file-server.ts +++ b/packages/module-federation/src/executors/utils/start-static-remotes-file-server.ts @@ -1,14 +1,15 @@ import { type ExecutorContext, workspaceRoot } from '@nx/devkit'; -import { type Schema } from '../schema'; +import { type StaticRemotesOptions } from './models'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import { join } from 'path'; import { cpSync } from 'fs'; -import type { StaticRemotesConfig } from '@nx/module-federation/src/utils'; +import type { StaticRemotesConfig } from '../../utils'; export function startStaticRemotesFileServer( staticRemotesConfig: StaticRemotesConfig, context: ExecutorContext, - options: Schema + options: StaticRemotesOptions, + forceMoveToCommonLocation = false ) { if ( !staticRemotesConfig.remotes || @@ -16,15 +17,17 @@ export function startStaticRemotesFileServer( ) { return; } - let shouldMoveToCommonLocation = false; + let shouldMoveToCommonLocation = forceMoveToCommonLocation || false; let commonOutputDirectory: string; - for (const app of staticRemotesConfig.remotes) { - const remoteBasePath = staticRemotesConfig.config[app].basePath; - if (!commonOutputDirectory) { - commonOutputDirectory = remoteBasePath; - } else if (commonOutputDirectory !== remoteBasePath) { - shouldMoveToCommonLocation = true; - break; + if (!forceMoveToCommonLocation) { + for (const app of staticRemotesConfig.remotes) { + const remoteBasePath = staticRemotesConfig.config[app].basePath; + if (!commonOutputDirectory) { + commonOutputDirectory = remoteBasePath; + } else if (commonOutputDirectory !== remoteBasePath) { + shouldMoveToCommonLocation = true; + break; + } } } @@ -62,3 +65,21 @@ export function startStaticRemotesFileServer( ); return staticRemotesIter; } + +export async function* startSsrStaticRemotesFileServer( + staticRemotesConfig: StaticRemotesConfig, + context: ExecutorContext, + options: StaticRemotesOptions +) { + const staticRemotesIter = startStaticRemotesFileServer( + staticRemotesConfig, + context, + options, + true + ); + if (!staticRemotesIter) { + yield { success: true }; + return; + } + yield* staticRemotesIter; +} diff --git a/packages/react/src/executors/module-federation-dev-server/lib/index.ts b/packages/react/src/executors/module-federation-dev-server/lib/index.ts new file mode 100644 index 0000000000000..7923ac83c9890 --- /dev/null +++ b/packages/react/src/executors/module-federation-dev-server/lib/index.ts @@ -0,0 +1,2 @@ +export * from './normalize-options'; +export * from './start-remotes'; diff --git a/packages/react/src/executors/module-federation-dev-server/lib/normalize-options.ts b/packages/react/src/executors/module-federation-dev-server/lib/normalize-options.ts new file mode 100644 index 0000000000000..1cebe6567eb20 --- /dev/null +++ b/packages/react/src/executors/module-federation-dev-server/lib/normalize-options.ts @@ -0,0 +1,29 @@ +import { + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nx/devkit'; +import { + ModuleFederationDevServerOptions, + NormalizedModuleFederationDevServerOptions, +} from '../schema'; + +export function getBuildOptions(buildTarget: string, context: ExecutorContext) { + const target = parseTargetString(buildTarget, context); + + const buildOptions = readTargetOptions(target, context); + + return { + ...buildOptions, + }; +} + +export function normalizeOptions( + options: ModuleFederationDevServerOptions +): NormalizedModuleFederationDevServerOptions { + return { + ...options, + devRemotes: options.devRemotes ?? [], + verbose: options.verbose ?? false, + }; +} diff --git a/packages/react/src/executors/module-federation-dev-server/lib/start-remotes.ts b/packages/react/src/executors/module-federation-dev-server/lib/start-remotes.ts new file mode 100644 index 0000000000000..d680317e152dc --- /dev/null +++ b/packages/react/src/executors/module-federation-dev-server/lib/start-remotes.ts @@ -0,0 +1,62 @@ +import { ExecutorContext, ProjectConfiguration, runExecutor } from '@nx/devkit'; +import { NormalizedModuleFederationDevServerOptions } from '../schema'; + +export async function startRemotes( + remotes: string[], + workspaceProjects: Record, + options: Pick< + NormalizedModuleFederationDevServerOptions, + 'devRemotes' | 'host' | 'ssl' | 'sslCert' | 'sslKey' | 'verbose' + >, + context: ExecutorContext, + target: 'serve' | 'serve-static' = 'serve' +) { + const remoteIters: AsyncIterable<{ success: boolean }>[] = []; + + for (const app of remotes) { + const remoteProjectServeTarget = workspaceProjects[app].targets[target]; + const isUsingModuleFederationDevServerExecutor = + remoteProjectServeTarget.executor.includes( + 'module-federation-dev-server' + ); + + const configurationOverride = options.devRemotes?.find( + ( + r + ): r is { + remoteName: string; + configuration: string; + } => typeof r !== 'string' && r.remoteName === app + )?.configuration; + + const defaultOverrides = { + ...(options.host ? { host: options.host } : {}), + ...(options.ssl ? { ssl: options.ssl } : {}), + ...(options.sslCert ? { sslCert: options.sslCert } : {}), + ...(options.sslKey ? { sslKey: options.sslKey } : {}), + }; + const overrides = + target === 'serve' + ? { + watch: true, + ...(isUsingModuleFederationDevServerExecutor + ? { isInitialHost: false } + : {}), + ...defaultOverrides, + } + : { ...defaultOverrides }; + + remoteIters.push( + await runExecutor( + { + project: app, + target, + configuration: configurationOverride ?? context.configurationName, + }, + overrides, + context + ) + ); + } + return remoteIters; +} 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 086cbd5a782da..59f01abdded67 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 @@ -1,162 +1,22 @@ -import { - ExecutorContext, - logger, - parseTargetString, - readTargetOptions, - runExecutor, - workspaceRoot, -} from '@nx/devkit'; +import { ExecutorContext, logger } from '@nx/devkit'; import devServerExecutor from '@nx/webpack/src/executors/dev-server/dev-server.impl'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import { ModuleFederationDevServerOptions } from './schema'; -import { - getModuleFederationConfig, - getRemotes, - startRemoteProxies, - parseStaticRemotesConfig, - type StaticRemotesConfig, -} from '@nx/module-federation/src/utils'; +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; import { combineAsyncIterables, createAsyncIterable, } from '@nx/devkit/src/utils/async-iterable'; import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; -import { cpSync, existsSync } from 'fs'; +import { existsSync } from 'fs'; import { extname, join } from 'path'; -import { buildStaticRemotes } from '../../utils/build-static.remotes'; - -function getBuildOptions(buildTarget: string, context: ExecutorContext) { - const target = parseTargetString(buildTarget, context); - - const buildOptions = readTargetOptions(target, context); - - return { - ...buildOptions, - }; -} - -function startStaticRemotesFileServer( - staticRemotesConfig: StaticRemotesConfig, - context: ExecutorContext, - options: ModuleFederationDevServerOptions -) { - if ( - !staticRemotesConfig.remotes || - staticRemotesConfig.remotes.length === 0 - ) { - return; - } - let shouldMoveToCommonLocation = false; - let commonOutputDirectory: string; - for (const app of staticRemotesConfig.remotes) { - const remoteBasePath = staticRemotesConfig.config[app].basePath; - if (!commonOutputDirectory) { - commonOutputDirectory = remoteBasePath; - } else if (commonOutputDirectory !== remoteBasePath) { - shouldMoveToCommonLocation = true; - break; - } - } - - if (shouldMoveToCommonLocation) { - commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes'); - for (const app of staticRemotesConfig.remotes) { - const remoteConfig = staticRemotesConfig.config[app]; - cpSync( - remoteConfig.outputPath, - join(commonOutputDirectory, remoteConfig.urlSegment), - { - force: true, - recursive: true, - } - ); - } - } - - const staticRemotesIter = fileServerExecutor( - { - cors: true, - watch: false, - staticFilePath: commonOutputDirectory, - parallel: false, - spa: false, - withDeps: false, - host: options.host, - port: options.staticRemotesPort, - ssl: options.ssl, - sslCert: options.sslCert, - sslKey: options.sslKey, - cacheSeconds: -1, - }, - context - ); - - return staticRemotesIter; -} - -async function startRemotes( - remotes: string[], - context: ExecutorContext, - options: ModuleFederationDevServerOptions, - target: 'serve' | 'serve-static' = 'serve' -) { - const remoteIters: AsyncIterable<{ success: boolean }>[] = []; - - for (const app of remotes) { - const remoteProjectServeTarget = - context.projectGraph.nodes[app].data.targets[target]; - const isUsingModuleFederationDevServerExecutor = - remoteProjectServeTarget.executor.includes( - 'module-federation-dev-server' - ); - - const configurationOverride = options.devRemotes?.find( - ( - r - ): r is { - remoteName: string; - configuration: string; - } => typeof r !== 'string' && r.remoteName === app - )?.configuration; - - const defaultOverrides = { - ...(options.host ? { host: options.host } : {}), - ...(options.ssl ? { ssl: options.ssl } : {}), - ...(options.sslCert ? { sslCert: options.sslCert } : {}), - ...(options.sslKey ? { sslKey: options.sslKey } : {}), - }; - const overrides = - target === 'serve' - ? { - watch: true, - ...(isUsingModuleFederationDevServerExecutor - ? { isInitialHost: false } - : {}), - ...defaultOverrides, - } - : { ...defaultOverrides }; - - remoteIters.push( - await runExecutor( - { - project: app, - target, - configuration: configurationOverride ?? context.configurationName, - }, - overrides, - context - ) - ); - } - return remoteIters; -} +import { getBuildOptions, normalizeOptions, startRemotes } from './lib'; export default async function* moduleFederationDevServer( - options: ModuleFederationDevServerOptions, + schema: ModuleFederationDevServerOptions, context: ExecutorContext ): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> { - // Force Node to resolve to look for the nx binary that is inside node_modules - const nxBin = require.resolve('nx/bin/nx'); + const options = normalizeOptions(schema); const currIter = options.static ? fileServerExecutor( { @@ -201,74 +61,14 @@ export default async function* moduleFederationDevServer( return yield* currIter; } - const moduleFederationConfig = getModuleFederationConfig( - buildOptions.tsConfig, - context.root, - p.root, - 'react' - ); - - const remoteNames = options.devRemotes?.map((r) => - typeof r === 'string' ? r : r.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: context.projectName, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - options.staticRemotesPort ??= remotes.staticRemotePort; - - // Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - remotes.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - p.name.replace(/-/g, '_'), - ]); - - const staticRemotesConfig = parseStaticRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - const mappedLocationsOfStaticRemotes = await buildStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - const devRemoteIters = await startRemotes( - remotes.devRemotes, - context, - options, - 'serve' - ); - - const staticRemotesIter = startStaticRemotesFileServer( - staticRemotesConfig, - context, - options - ); - - startRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { - pathToCert: join(workspaceRoot, options.sslCert), - pathToKey: join(workspaceRoot, options.sslKey), - } - : undefined - ); + const { staticRemotesIter, devRemoteIters, remotes } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'react' + ); return yield* combineAsyncIterables( currIter, diff --git a/packages/react/src/executors/module-federation-dev-server/schema.d.ts b/packages/react/src/executors/module-federation-dev-server/schema.d.ts index 38a7266e98299..61bbca581ea9d 100644 --- a/packages/react/src/executors/module-federation-dev-server/schema.d.ts +++ b/packages/react/src/executors/module-federation-dev-server/schema.d.ts @@ -1,17 +1,19 @@ import { WebDevServerOptions } from '@nx/webpack'; +import { DevRemoteDefinition } from '@nx/module-federation/src/executors/utils'; export type ModuleFederationDevServerOptions = WebDevServerOptions & { - devRemotes?: ( - | string - | { - remoteName: string; - configuration: string; - } - )[]; + devRemotes?: DevRemoteDefinition[]; skipRemotes?: string[]; static?: boolean; isInitialHost?: boolean; parallel?: number; staticRemotesPort?: number; pathToManifestFile?: string; + verbose?: boolean; }; + +export type NormalizedModuleFederationDevServerOptions = + ModuleFederationDevServerOptions & { + devRemotes: DevRemoteDefinition[]; + verbose: boolean; + }; diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/lib/index.ts b/packages/react/src/executors/module-federation-ssr-dev-server/lib/index.ts new file mode 100644 index 0000000000000..7923ac83c9890 --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/lib/index.ts @@ -0,0 +1,2 @@ +export * from './normalize-options'; +export * from './start-remotes'; diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts b/packages/react/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts new file mode 100644 index 0000000000000..d3b030d22adcb --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts @@ -0,0 +1,34 @@ +import { + ModuleFederationSsrDevServerOptions, + NormalizedModuleFederationSsrDevServerOptions, +} from '../schema'; +import { join } from 'path'; +import { + workspaceRoot, + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nx/devkit'; + +export function normalizeOptions( + options: ModuleFederationSsrDevServerOptions +): NormalizedModuleFederationSsrDevServerOptions { + return { + ...options, + devRemotes: options.devRemotes ?? [], + verbose: options.verbose ?? false, + ssl: options.ssl ?? false, + sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined, + sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined, + }; +} + +export function getBuildOptions(buildTarget: string, context: ExecutorContext) { + const target = parseTargetString(buildTarget, context); + + const buildOptions = readTargetOptions(target, context); + + return { + ...buildOptions, + }; +} diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts b/packages/react/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts new file mode 100644 index 0000000000000..6072069beb368 --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts @@ -0,0 +1,58 @@ +import { ModuleFederationSsrDevServerOptions } from '../schema'; +import { runExecutor, ExecutorContext, ProjectConfiguration } from '@nx/devkit'; + +export async function startRemotes( + remotes: string[], + workspaceProjects: Record, + options: Partial< + Pick< + ModuleFederationSsrDevServerOptions, + 'devRemotes' | 'host' | 'ssl' | 'sslCert' | 'sslKey' | 'verbose' + > + >, + context: ExecutorContext +) { + const remoteIters: AsyncIterable<{ success: boolean }>[] = []; + const target = 'serve'; + for (const app of remotes) { + const remoteProjectServeTarget = workspaceProjects[app].targets[target]; + const isUsingModuleFederationSsrDevServerExecutor = + remoteProjectServeTarget.executor.includes( + 'module-federation-ssr-dev-server' + ); + + const configurationOverride = options.devRemotes?.find( + (remote): remote is { remoteName: string; configuration: string } => + typeof remote !== 'string' && remote.remoteName === app + )?.configuration; + { + const defaultOverrides = { + ...(options.host ? { host: options.host } : {}), + ...(options.ssl ? { ssl: options.ssl } : {}), + ...(options.sslCert ? { sslCert: options.sslCert } : {}), + ...(options.sslKey ? { sslKey: options.sslKey } : {}), + }; + + const overrides = { + watch: true, + ...defaultOverrides, + ...(isUsingModuleFederationSsrDevServerExecutor + ? { isInitialHost: false } + : {}), + }; + + remoteIters.push( + await runExecutor( + { + project: app, + target, + configuration: configurationOverride ?? context.configurationName, + }, + overrides, + context + ) + ); + } + } + return remoteIters; +} diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts index 2d8369c26fe1e..2580abb00d217 100644 --- a/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts +++ b/packages/react/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts @@ -1,257 +1,21 @@ -import { - ExecutorContext, - logger, - parseTargetString, - readTargetOptions, - runExecutor, - workspaceRoot, -} from '@nx/devkit'; +import { ExecutorContext, logger } from '@nx/devkit'; import ssrDevServerExecutor from '@nx/webpack/src/executors/ssr-dev-server/ssr-dev-server.impl'; -import { WebSsrDevServerOptions } from '@nx/webpack/src/executors/ssr-dev-server/schema'; import { extname, join } from 'path'; -import { - getModuleFederationConfig, - getRemotes, - parseStaticSsrRemotesConfig, - type StaticRemotesConfig, - startSsrRemoteProxies, -} from '@nx/module-federation/src/utils'; - +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; import { combineAsyncIterables, createAsyncIterable, } from '@nx/devkit/src/utils/async-iterable'; -import { fork } from 'child_process'; -import { cpSync, createWriteStream, existsSync } from 'fs'; - -import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; -import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; +import { existsSync } from 'fs'; import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; - -type ModuleFederationSsrDevServerOptions = WebSsrDevServerOptions & { - devRemotes?: ( - | string - | { - remoteName: string; - configuration: string; - } - )[]; - - skipRemotes?: string[]; - host: string; - pathToManifestFile?: string; - staticRemotesPort?: number; - parallel?: number; - ssl?: boolean; - sslKey?: string; - sslCert?: string; - isInitialHost?: boolean; -}; - -function normalizeOptions( - options: ModuleFederationSsrDevServerOptions -): ModuleFederationSsrDevServerOptions { - return { - ...options, - ssl: options.ssl ?? false, - sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined, - sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined, - }; -} - -function getBuildOptions(buildTarget: string, context: ExecutorContext) { - const target = parseTargetString(buildTarget, context); - - const buildOptions = readTargetOptions(target, context); - - return { - ...buildOptions, - }; -} - -async function* startSsrStaticRemotesFileServer( - ssrStaticRemotesConfig: StaticRemotesConfig, - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -): - | AsyncGenerator<{ success: boolean; baseUrl?: string }> - | AsyncIterable<{ success: boolean; baseUrl?: string }> { - if (ssrStaticRemotesConfig.remotes.length === 0) { - yield { success: true }; - return; - } - - // The directories are usually generated with /browser and /server suffixes so we need to copy them to a common directory - const commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes'); - for (const app of ssrStaticRemotesConfig.remotes) { - const remoteConfig = ssrStaticRemotesConfig.config[app]; - - cpSync( - remoteConfig.outputPath, - join(commonOutputDirectory, remoteConfig.urlSegment), - { - force: true, - recursive: true, - } - ); - } - - const staticRemotesIter = fileServerExecutor( - { - cors: true, - watch: false, - staticFilePath: commonOutputDirectory, - parallel: false, - spa: false, - withDeps: false, - host: options.host, - port: options.staticRemotesPort, - ssl: options.ssl, - sslCert: options.sslCert, - sslKey: options.sslKey, - cacheSeconds: -1, - }, - context - ); - - yield* staticRemotesIter; -} - -async function startRemotes( - remotes: string[], - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -) { - const remoteIters: AsyncIterable<{ success: boolean }>[] = []; - const target = 'serve'; - for (const app of remotes) { - const remoteProjectServeTarget = - context.projectGraph.nodes[app].data.targets[target]; - const isUsingModuleFederationSsrDevServerExecutor = - remoteProjectServeTarget.executor.includes( - 'module-federation-ssr-dev-server' - ); - - const configurationOverride = options.devRemotes?.find( - (remote): remote is { remoteName: string; configuration: string } => - typeof remote !== 'string' && remote.remoteName === app - )?.configuration; - { - const defaultOverrides = { - ...(options.host ? { host: options.host } : {}), - ...(options.ssl ? { ssl: options.ssl } : {}), - ...(options.sslCert ? { sslCert: options.sslCert } : {}), - ...(options.sslKey ? { sslKey: options.sslKey } : {}), - }; - - const overrides = { - watch: true, - ...defaultOverrides, - ...(isUsingModuleFederationSsrDevServerExecutor - ? { isInitialHost: false } - : {}), - }; - - remoteIters.push( - await runExecutor( - { - project: app, - target, - configuration: configurationOverride ?? context.configurationName, - }, - overrides, - context - ) - ); - } - } - return remoteIters; -} - -async function buildSsrStaticRemotes( - staticRemotesConfig: StaticRemotesConfig, - nxBin, - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -) { - if (!staticRemotesConfig.remotes.length) { - return; - } - - logger.info( - `Nx is building ${staticRemotesConfig.remotes.length} static remotes...` - ); - const mapLocationOfRemotes: Record = {}; - - for (const remoteApp of staticRemotesConfig.remotes) { - mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${ - options.host - }:${options.staticRemotesPort}/${ - staticRemotesConfig.config[remoteApp].urlSegment - }`; - } - - await new Promise((resolve) => { - const childProcess = fork( - nxBin, - [ - 'run-many', - '--target=server', - '--projects', - staticRemotesConfig.remotes.join(','), - ...(context.configurationName - ? [`--configuration=${context.configurationName}`] - : []), - ...(options.parallel ? [`--parallel=${options.parallel}`] : []), - ], - { - cwd: context.root, - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - } - ); - - // Add a listener to the child process to capture the build log - const remoteBuildLogFile = join( - workspaceDataDirectory, - `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` - ); - - const remoteBuildLogStream = createWriteStream(remoteBuildLogFile); - - childProcess.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, ''); - remoteBuildLogStream.write(stdoutString); - - // in addition to writing into the stdout stream, also show error directly in console - // so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output. - if (stdoutString.includes('ERROR in')) { - logger.log(stdoutString); - } - - if (stdoutString.includes('Successfully ran target server')) { - childProcess.stdout.removeAllListeners('data'); - logger.info( - `Nx Built ${staticRemotesConfig.remotes.length} static remotes.` - ); - resolve(); - } - }); - - process.on('SIGTERM', () => childProcess.kill('SIGTERM')); - process.on('exit', () => childProcess.kill('SIGTERM')); - }); - return mapLocationOfRemotes; -} +import { ModuleFederationSsrDevServerOptions } from './schema'; +import { getBuildOptions, normalizeOptions, startRemotes } from './lib'; export default async function* moduleFederationSsrDevServer( ssrDevServerOptions: ModuleFederationSsrDevServerOptions, context: ExecutorContext ) { const options = normalizeOptions(ssrDevServerOptions); - // Force Node to resolve to look for the nx binary that is inside node_modules - const nxBin = require.resolve('nx/bin/nx'); let iter: any = ssrDevServerExecutor(options, context); const projectConfig = context.projectsConfigurations.projects[context.projectName]; @@ -285,74 +49,15 @@ export default async function* moduleFederationSsrDevServer( return yield* iter; } - const moduleFederationConfig = getModuleFederationConfig( - buildOptions.tsConfig, - context.root, - projectConfig.root, - 'react' - ); - - const remoteNames = options.devRemotes?.map((remote) => - typeof remote === 'string' ? remote : remote.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: context.projectName, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - - options.staticRemotesPort ??= remotes.staticRemotePort; - - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - remotes.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - projectConfig.name.replace(/-/g, '_'), - ]); - - const staticRemotesConfig = parseStaticSsrRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - - const mappedLocationsOfStaticRemotes = await buildSsrStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - const devRemoteIters = await startRemotes( - remotes.devRemotes, - context, - options - ); - - const staticRemotesIter = startSsrStaticRemotesFileServer( - staticRemotesConfig, - context, - options - ); - - startSsrRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { - pathToCert: options.sslCert, - pathToKey: options.sslKey, - } - : undefined - ); + const { staticRemotesIter, devRemoteIters, remotes } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'react', + true + ); const combined = combineAsyncIterables(staticRemotesIter, ...devRemoteIters); diff --git a/packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts b/packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts new file mode 100644 index 0000000000000..d37070f0209cb --- /dev/null +++ b/packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts @@ -0,0 +1,29 @@ +import { WebSsrDevServerOptions } from '@nx/webpack/src/executors/ssr-dev-server/schema'; +import { DevRemoteDefinition } from '@nx/module-federation/src/executors/utils'; + +export type ModuleFederationSsrDevServerOptions = WebSsrDevServerOptions & { + devRemotes?: ( + | string + | { + remoteName: string; + configuration: string; + } + )[]; + + skipRemotes?: string[]; + host: string; + pathToManifestFile?: string; + staticRemotesPort?: number; + parallel?: number; + ssl?: boolean; + sslKey?: string; + sslCert?: string; + isInitialHost?: boolean; + verbose?: boolean; +}; + +export type NormalizedModuleFederationSsrDevServerOptions = + ModuleFederationSsrDevServerOptions & { + devRemotes: DevRemoteDefinition[]; + verbose: boolean; + }; diff --git a/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts b/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts index db32670306f57..c48826c594af9 100644 --- a/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts +++ b/packages/react/src/executors/module-federation-static-server/module-federation-static-server.impl.ts @@ -16,7 +16,7 @@ import { parseStaticRemotesConfig, StaticRemotesConfig, } from '@nx/module-federation/src/utils'; -import { buildStaticRemotes } from '../../utils/build-static.remotes'; +import { buildStaticRemotes } from '@nx/module-federation/src/executors/utils'; import { fork } from 'child_process'; import type { WebpackExecutorOptions } from '@nx/webpack'; import * as process from 'node:process'; diff --git a/packages/rspack/src/executors/module-federation-dev-server/lib/index.ts b/packages/rspack/src/executors/module-federation-dev-server/lib/index.ts new file mode 100644 index 0000000000000..7923ac83c9890 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/lib/index.ts @@ -0,0 +1,2 @@ +export * from './normalize-options'; +export * from './start-remotes'; diff --git a/packages/rspack/src/executors/module-federation-dev-server/lib/normalize-options.ts b/packages/rspack/src/executors/module-federation-dev-server/lib/normalize-options.ts new file mode 100644 index 0000000000000..1cebe6567eb20 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/lib/normalize-options.ts @@ -0,0 +1,29 @@ +import { + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nx/devkit'; +import { + ModuleFederationDevServerOptions, + NormalizedModuleFederationDevServerOptions, +} from '../schema'; + +export function getBuildOptions(buildTarget: string, context: ExecutorContext) { + const target = parseTargetString(buildTarget, context); + + const buildOptions = readTargetOptions(target, context); + + return { + ...buildOptions, + }; +} + +export function normalizeOptions( + options: ModuleFederationDevServerOptions +): NormalizedModuleFederationDevServerOptions { + return { + ...options, + devRemotes: options.devRemotes ?? [], + verbose: options.verbose ?? false, + }; +} diff --git a/packages/rspack/src/executors/module-federation-dev-server/lib/start-remotes.ts b/packages/rspack/src/executors/module-federation-dev-server/lib/start-remotes.ts new file mode 100644 index 0000000000000..a64079d44a37b --- /dev/null +++ b/packages/rspack/src/executors/module-federation-dev-server/lib/start-remotes.ts @@ -0,0 +1,64 @@ +import { ModuleFederationDevServerOptions } from '../schema'; +import { ProjectConfiguration, ExecutorContext, runExecutor } from '@nx/devkit'; + +export async function startRemotes( + remotes: string[], + workspaceProjects: Record, + options: Partial< + Pick< + ModuleFederationDevServerOptions, + 'devRemotes' | 'host' | 'ssl' | 'sslCert' | 'sslKey' | 'verbose' + > + >, + context: ExecutorContext, + target: 'serve' | 'serve-static' = 'serve' +) { + const remoteIters: AsyncIterable<{ success: boolean }>[] = []; + + for (const app of remotes) { + const remoteProjectServeTarget = workspaceProjects[app].targets[target]; + const isUsingModuleFederationDevServerExecutor = + remoteProjectServeTarget.executor.includes( + 'module-federation-dev-server' + ); + + const configurationOverride = options.devRemotes?.find( + ( + r + ): r is { + remoteName: string; + configuration: string; + } => typeof r !== 'string' && r.remoteName === app + )?.configuration; + + const defaultOverrides = { + ...(options.host ? { host: options.host } : {}), + ...(options.ssl ? { ssl: options.ssl } : {}), + ...(options.sslCert ? { sslCert: options.sslCert } : {}), + ...(options.sslKey ? { sslKey: options.sslKey } : {}), + }; + const overrides = + target === 'serve' + ? { + watch: true, + ...(isUsingModuleFederationDevServerExecutor + ? { isInitialHost: false } + : {}), + ...defaultOverrides, + } + : { ...defaultOverrides }; + + remoteIters.push( + await runExecutor( + { + project: app, + target, + configuration: configurationOverride ?? context.configurationName, + }, + overrides, + context + ) + ); + } + return remoteIters; +} diff --git a/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts b/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts index 62db9b91f4a7f..2d00b01a3eae6 100644 --- a/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts +++ b/packages/rspack/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts @@ -1,162 +1,22 @@ -import { - ExecutorContext, - logger, - parseTargetString, - readTargetOptions, - runExecutor, - workspaceRoot, -} from '@nx/devkit'; +import { ExecutorContext, logger } from '@nx/devkit'; import { combineAsyncIterables, createAsyncIterable, } from '@nx/devkit/src/utils/async-iterable'; import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; -import { cpSync, existsSync } from 'fs'; +import { existsSync } from 'fs'; import { extname, join } from 'path'; -import { - getModuleFederationConfig, - getRemotes, - parseStaticRemotesConfig, - type StaticRemotesConfig, - startRemoteProxies, -} from '@nx/module-federation/src/utils'; -import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes'; +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; import devServerExecutor from '../dev-server/dev-server.impl'; import { ModuleFederationDevServerOptions } from './schema'; - -function getBuildOptions(buildTarget: string, context: ExecutorContext) { - const target = parseTargetString(buildTarget, context); - - const buildOptions = readTargetOptions(target, context); - - return { - ...buildOptions, - }; -} - -function startStaticRemotesFileServer( - staticRemotesConfig: StaticRemotesConfig, - context: ExecutorContext, - options: ModuleFederationDevServerOptions -) { - if ( - !staticRemotesConfig.remotes || - staticRemotesConfig.remotes.length === 0 - ) { - return; - } - let shouldMoveToCommonLocation = false; - let commonOutputDirectory: string; - for (const app of staticRemotesConfig.remotes) { - const remoteBasePath = staticRemotesConfig.config[app].basePath; - if (!commonOutputDirectory) { - commonOutputDirectory = remoteBasePath; - } else if (commonOutputDirectory !== remoteBasePath) { - shouldMoveToCommonLocation = true; - break; - } - } - - if (shouldMoveToCommonLocation) { - commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes'); - for (const app of staticRemotesConfig.remotes) { - const remoteConfig = staticRemotesConfig.config[app]; - cpSync( - remoteConfig.outputPath, - join(commonOutputDirectory, remoteConfig.urlSegment), - { - force: true, - recursive: true, - } - ); - } - } - - const staticRemotesIter = fileServerExecutor( - { - cors: true, - watch: false, - staticFilePath: commonOutputDirectory, - parallel: false, - spa: false, - withDeps: false, - host: options.host, - port: options.staticRemotesPort, - ssl: options.ssl, - sslCert: options.sslCert, - sslKey: options.sslKey, - cacheSeconds: -1, - }, - context - ); - - return staticRemotesIter; -} - -async function startRemotes( - remotes: string[], - context: ExecutorContext, - options: ModuleFederationDevServerOptions, - target: 'serve' | 'serve-static' = 'serve' -) { - const remoteIters: AsyncIterable<{ success: boolean }>[] = []; - - for (const app of remotes) { - const remoteProjectServeTarget = - context.projectGraph.nodes[app].data.targets[target]; - const isUsingModuleFederationDevServerExecutor = - remoteProjectServeTarget.executor.includes( - 'module-federation-dev-server' - ); - - const configurationOverride = options.devRemotes?.find( - ( - r - ): r is { - remoteName: string; - configuration: string; - } => typeof r !== 'string' && r.remoteName === app - )?.configuration; - - const defaultOverrides = { - ...(options.host ? { host: options.host } : {}), - ...(options.ssl ? { ssl: options.ssl } : {}), - ...(options.sslCert ? { sslCert: options.sslCert } : {}), - ...(options.sslKey ? { sslKey: options.sslKey } : {}), - }; - const overrides = - target === 'serve' - ? { - watch: true, - ...(isUsingModuleFederationDevServerExecutor - ? { isInitialHost: false } - : {}), - ...defaultOverrides, - } - : { ...defaultOverrides }; - - remoteIters.push( - await runExecutor( - { - project: app, - target, - configuration: configurationOverride ?? context.configurationName, - }, - overrides, - context - ) - ); - } - return remoteIters; -} +import { getBuildOptions, normalizeOptions, startRemotes } from './lib'; export default async function* moduleFederationDevServer( - options: ModuleFederationDevServerOptions, + schema: ModuleFederationDevServerOptions, context: ExecutorContext ): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> { - // Force Node to resolve to look for the nx binary that is inside node_modules - const nxBin = require.resolve('nx/bin/nx'); + const options = normalizeOptions(schema); const currIter = options.static ? fileServerExecutor( { @@ -201,74 +61,14 @@ export default async function* moduleFederationDevServer( return yield* currIter; } - const moduleFederationConfig = getModuleFederationConfig( - buildOptions.tsConfig, - context.root, - p.root, - 'react' - ); - - const remoteNames = options.devRemotes?.map((r) => - typeof r === 'string' ? r : r.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: context.projectName, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - options.staticRemotesPort ??= remotes.staticRemotePort; - - // Set NX_MF_DEV_REMOTES for the Nx Runtime Library Control Plugin - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - remotes.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - p.name.replace(/-/g, '_'), - ]); - - const staticRemotesConfig = parseStaticRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - const mappedLocationsOfStaticRemotes = await buildStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - const devRemoteIters = await startRemotes( - remotes.devRemotes, - context, - options, - 'serve' - ); - - const staticRemotesIter = startStaticRemotesFileServer( - staticRemotesConfig, - context, - options - ); - - startRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { - pathToCert: join(workspaceRoot, options.sslCert), - pathToKey: join(workspaceRoot, options.sslKey), - } - : undefined - ); + const { staticRemotesIter, devRemoteIters, remotes } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'react' + ); return yield* combineAsyncIterables( currIter, diff --git a/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts b/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts index 61c4c0657eb06..6df7a070173cd 100644 --- a/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts +++ b/packages/rspack/src/executors/module-federation-dev-server/schema.d.ts @@ -15,4 +15,11 @@ export type ModuleFederationDevServerOptions = DevServerExecutorSchema & { parallel?: number; staticRemotesPort?: number; pathToManifestFile?: string; + verbose?: boolean; }; + +export type NormalizedModuleFederationDevServerOptions = + ModuleFederationDevServerOptions & { + devRemotes: DevServerExecutorSchema['devRemotes']; + verbose: boolean; + }; diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/index.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/index.ts new file mode 100644 index 0000000000000..7923ac83c9890 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/index.ts @@ -0,0 +1,2 @@ +export * from './normalize-options'; +export * from './start-remotes'; diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts new file mode 100644 index 0000000000000..d3b030d22adcb --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/normalize-options.ts @@ -0,0 +1,34 @@ +import { + ModuleFederationSsrDevServerOptions, + NormalizedModuleFederationSsrDevServerOptions, +} from '../schema'; +import { join } from 'path'; +import { + workspaceRoot, + ExecutorContext, + parseTargetString, + readTargetOptions, +} from '@nx/devkit'; + +export function normalizeOptions( + options: ModuleFederationSsrDevServerOptions +): NormalizedModuleFederationSsrDevServerOptions { + return { + ...options, + devRemotes: options.devRemotes ?? [], + verbose: options.verbose ?? false, + ssl: options.ssl ?? false, + sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined, + sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined, + }; +} + +export function getBuildOptions(buildTarget: string, context: ExecutorContext) { + const target = parseTargetString(buildTarget, context); + + const buildOptions = readTargetOptions(target, context); + + return { + ...buildOptions, + }; +} diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts new file mode 100644 index 0000000000000..6072069beb368 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/lib/start-remotes.ts @@ -0,0 +1,58 @@ +import { ModuleFederationSsrDevServerOptions } from '../schema'; +import { runExecutor, ExecutorContext, ProjectConfiguration } from '@nx/devkit'; + +export async function startRemotes( + remotes: string[], + workspaceProjects: Record, + options: Partial< + Pick< + ModuleFederationSsrDevServerOptions, + 'devRemotes' | 'host' | 'ssl' | 'sslCert' | 'sslKey' | 'verbose' + > + >, + context: ExecutorContext +) { + const remoteIters: AsyncIterable<{ success: boolean }>[] = []; + const target = 'serve'; + for (const app of remotes) { + const remoteProjectServeTarget = workspaceProjects[app].targets[target]; + const isUsingModuleFederationSsrDevServerExecutor = + remoteProjectServeTarget.executor.includes( + 'module-federation-ssr-dev-server' + ); + + const configurationOverride = options.devRemotes?.find( + (remote): remote is { remoteName: string; configuration: string } => + typeof remote !== 'string' && remote.remoteName === app + )?.configuration; + { + const defaultOverrides = { + ...(options.host ? { host: options.host } : {}), + ...(options.ssl ? { ssl: options.ssl } : {}), + ...(options.sslCert ? { sslCert: options.sslCert } : {}), + ...(options.sslKey ? { sslKey: options.sslKey } : {}), + }; + + const overrides = { + watch: true, + ...defaultOverrides, + ...(isUsingModuleFederationSsrDevServerExecutor + ? { isInitialHost: false } + : {}), + }; + + remoteIters.push( + await runExecutor( + { + project: app, + target, + configuration: configurationOverride ?? context.configurationName, + }, + overrides, + context + ) + ); + } + } + return remoteIters; +} diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts index edecccbeffb73..5e8b673ac5332 100644 --- a/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl.ts @@ -1,256 +1,22 @@ -import { - ExecutorContext, - logger, - parseTargetString, - readTargetOptions, - runExecutor, - workspaceRoot, -} from '@nx/devkit'; +import { ExecutorContext, logger } from '@nx/devkit'; import { extname, join } from 'path'; -import { - getModuleFederationConfig, - getRemotes, - parseStaticSsrRemotesConfig, - type StaticRemotesConfig, - startSsrRemoteProxies, -} from '@nx/module-federation/src/utils'; -import { RspackSsrDevServerOptions } from '../ssr-dev-server/schema'; +import { startRemoteIterators } from '@nx/module-federation/src/executors/utils'; import ssrDevServerExecutor from '../ssr-dev-server/ssr-dev-server.impl'; - import { combineAsyncIterables, createAsyncIterable, } from '@nx/devkit/src/utils/async-iterable'; -import { fork } from 'child_process'; -import { cpSync, createWriteStream, existsSync } from 'fs'; - -import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl'; +import { existsSync } from 'fs'; import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open'; -import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; - -type ModuleFederationSsrDevServerOptions = RspackSsrDevServerOptions & { - devRemotes?: ( - | string - | { - remoteName: string; - configuration: string; - } - )[]; - - skipRemotes?: string[]; - host: string; - pathToManifestFile?: string; - staticRemotesPort?: number; - parallel?: number; - ssl?: boolean; - sslKey?: string; - sslCert?: string; - isInitialHost?: boolean; -}; - -function normalizeOptions( - options: ModuleFederationSsrDevServerOptions -): ModuleFederationSsrDevServerOptions { - return { - ...options, - ssl: options.ssl ?? false, - sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined, - sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined, - }; -} - -function getBuildOptions(buildTarget: string, context: ExecutorContext) { - const target = parseTargetString(buildTarget, context); - - const buildOptions = readTargetOptions(target, context); - - return { - ...buildOptions, - }; -} - -function startSsrStaticRemotesFileServer( - ssrStaticRemotesConfig: StaticRemotesConfig, - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -) { - if (ssrStaticRemotesConfig.remotes.length === 0) { - return; - } - - // The directories are usually generated with /browser and /server suffixes so we need to copy them to a common directory - const commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes'); - for (const app of ssrStaticRemotesConfig.remotes) { - const remoteConfig = ssrStaticRemotesConfig.config[app]; - - cpSync( - remoteConfig.outputPath, - join(commonOutputDirectory, remoteConfig.urlSegment), - { - force: true, - recursive: true, - } - ); - } - - const staticRemotesIter = fileServerExecutor( - { - cors: true, - watch: false, - staticFilePath: commonOutputDirectory, - parallel: false, - spa: false, - withDeps: false, - host: options.host, - port: options.staticRemotesPort, - ssl: options.ssl, - sslCert: options.sslCert, - sslKey: options.sslKey, - cacheSeconds: -1, - }, - context - ); - - return staticRemotesIter; -} - -async function startRemotes( - remotes: string[], - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -) { - const remoteIters: AsyncIterable<{ success: boolean }>[] = []; - const target = 'serve'; - for (const app of remotes) { - const remoteProjectServeTarget = - context.projectGraph.nodes[app].data.targets[target]; - const isUsingModuleFederationSsrDevServerExecutor = - remoteProjectServeTarget.executor.includes( - 'module-federation-ssr-dev-server' - ); - - const configurationOverride = options.devRemotes?.find( - (remote): remote is { remoteName: string; configuration: string } => - typeof remote !== 'string' && remote.remoteName === app - )?.configuration; - { - const defaultOverrides = { - ...(options.host ? { host: options.host } : {}), - ...(options.ssl ? { ssl: options.ssl } : {}), - ...(options.sslCert ? { sslCert: options.sslCert } : {}), - ...(options.sslKey ? { sslKey: options.sslKey } : {}), - }; - - const overrides = { - watch: true, - ...defaultOverrides, - ...(isUsingModuleFederationSsrDevServerExecutor - ? { isInitialHost: false } - : {}), - }; - - remoteIters.push( - await runExecutor( - { - project: app, - target, - configuration: configurationOverride ?? context.configurationName, - }, - overrides, - context - ) - ); - } - } - return remoteIters; -} - -async function buildSsrStaticRemotes( - staticRemotesConfig: StaticRemotesConfig, - nxBin, - context: ExecutorContext, - options: ModuleFederationSsrDevServerOptions -) { - if (!staticRemotesConfig.remotes.length) { - return; - } - - logger.info( - `Nx is building ${staticRemotesConfig.remotes.length} static remotes...` - ); - const mapLocationOfRemotes: Record = {}; - - for (const remoteApp of staticRemotesConfig.remotes) { - mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${ - options.host - }:${options.staticRemotesPort}/${ - staticRemotesConfig.config[remoteApp].urlSegment - }`; - } - - await new Promise((resolve) => { - const childProcess = fork( - nxBin, - [ - 'run-many', - '--target=server', - '--projects', - staticRemotesConfig.remotes.join(','), - ...(context.configurationName - ? [`--configuration=${context.configurationName}`] - : []), - ...(options.parallel ? [`--parallel=${options.parallel}`] : []), - ], - { - cwd: context.root, - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - } - ); - - // Add a listener to the child process to capture the build log - const remoteBuildLogFile = join( - workspaceDataDirectory, - // eslint-disable-next-line - `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` - ); - - const remoteBuildLogStream = createWriteStream(remoteBuildLogFile); - - childProcess.stdout.on('data', (data) => { - const ANSII_CODE_REGEX = - // eslint-disable-next-line no-control-regex - /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; - const stdoutString = data.toString().replace(ANSII_CODE_REGEX, ''); - remoteBuildLogStream.write(stdoutString); - - // in addition to writing into the stdout stream, also show error directly in console - // so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output. - if (stdoutString.includes('ERROR in')) { - logger.log(stdoutString); - } - - if (stdoutString.includes('Successfully ran target server')) { - childProcess.stdout.removeAllListeners('data'); - logger.info( - `Nx Built ${staticRemotesConfig.remotes.length} static remotes.` - ); - resolve(); - } - }); - - process.on('SIGTERM', () => childProcess.kill('SIGTERM')); - process.on('exit', () => childProcess.kill('SIGTERM')); - }); - return mapLocationOfRemotes; -} +import { ModuleFederationSsrDevServerOptions } from './schema'; +import { getBuildOptions, normalizeOptions, startRemotes } from './lib'; export default async function* moduleFederationSsrDevServer( ssrDevServerOptions: ModuleFederationSsrDevServerOptions, context: ExecutorContext ) { const options = normalizeOptions(ssrDevServerOptions); - // Force Node to resolve to look for the nx binary that is inside node_modules - const nxBin = require.resolve('nx/bin/nx'); + const iter = ssrDevServerExecutor(options, context); const projectConfig = context.projectsConfigurations.projects[context.projectName]; @@ -284,74 +50,15 @@ export default async function* moduleFederationSsrDevServer( return yield* iter; } - const moduleFederationConfig = getModuleFederationConfig( - buildOptions.tsConfig, - context.root, - projectConfig.root, - 'react' - ); - - const remoteNames = options.devRemotes?.map((remote) => - typeof remote === 'string' ? remote : remote.remoteName - ); - - const remotes = getRemotes( - remoteNames, - options.skipRemotes, - moduleFederationConfig, - { - projectName: context.projectName, - projectGraph: context.projectGraph, - root: context.root, - }, - pathToManifestFile - ); - - options.staticRemotesPort ??= remotes.staticRemotePort; - - process.env.NX_MF_DEV_REMOTES = JSON.stringify([ - ...( - remotes.devRemotes.map((r) => - typeof r === 'string' ? r : r.remoteName - ) ?? [] - ).map((r) => r.replace(/-/g, '_')), - projectConfig.name.replace(/-/g, '_'), - ]); - - const staticRemotesConfig = parseStaticSsrRemotesConfig( - [...remotes.staticRemotes, ...remotes.dynamicRemotes], - context - ); - - const mappedLocationsOfStaticRemotes = await buildSsrStaticRemotes( - staticRemotesConfig, - nxBin, - context, - options - ); - - const devRemoteIters = await startRemotes( - remotes.devRemotes, - context, - options - ); - - const staticRemotesIter = startSsrStaticRemotesFileServer( - staticRemotesConfig, - context, - options - ); - - startSsrRemoteProxies( - staticRemotesConfig, - mappedLocationsOfStaticRemotes, - options.ssl - ? { - pathToCert: options.sslCert, - pathToKey: options.sslKey, - } - : undefined - ); + const { staticRemotesIter, devRemoteIters, remotes } = + await startRemoteIterators( + options, + context, + startRemotes, + pathToManifestFile, + 'react', + true + ); return yield* combineAsyncIterables( iter, diff --git a/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts b/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts new file mode 100644 index 0000000000000..8df7a459c72e1 --- /dev/null +++ b/packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts @@ -0,0 +1,29 @@ +import { DevRemoteDefinition } from '@nx/module-federation/src/executors/utils'; +import { RspackSsrDevServerOptions } from '../ssr-dev-server/schema'; + +export type ModuleFederationSsrDevServerOptions = RspackSsrDevServerOptions & { + devRemotes?: ( + | string + | { + remoteName: string; + configuration: string; + } + )[]; + + skipRemotes?: string[]; + host: string; + pathToManifestFile?: string; + staticRemotesPort?: number; + parallel?: number; + ssl?: boolean; + sslKey?: string; + sslCert?: string; + isInitialHost?: boolean; + verbose?: boolean; +}; + +export type NormalizedModuleFederationSsrDevServerOptions = + ModuleFederationSsrDevServerOptions & { + devRemotes: DevRemoteDefinition[]; + verbose: boolean; + }; diff --git a/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts index 3032cd49da325..5c93f1dc1c9c3 100644 --- a/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts +++ b/packages/rspack/src/executors/module-federation-static-server/module-federation-static-server.impl.ts @@ -22,7 +22,7 @@ import { parseStaticRemotesConfig, StaticRemotesConfig, } from '@nx/module-federation/src/utils'; -import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes'; +import { buildStaticRemotes } from '@nx/module-federation/src/executors/utils'; import { ModuleFederationDevServerOptions } from '../module-federation-dev-server/schema'; import type { RspackExecutorSchema } from '../rspack/schema'; import { ModuleFederationStaticServerSchema } from './schema'; diff --git a/packages/rspack/src/utils/module-federation/build-static.remotes.ts b/packages/rspack/src/utils/module-federation/build-static.remotes.ts deleted file mode 100644 index 2db02836bed87..0000000000000 --- a/packages/rspack/src/utils/module-federation/build-static.remotes.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { ExecutorContext } from '@nx/devkit'; -import { createWriteStream } from 'fs'; -import { fork } from 'node:child_process'; -import { workspaceDataDirectory } from 'nx/src/utils/cache-directory'; -import { logger } from 'nx/src/utils/logger'; -import { join } from 'path'; -import { ModuleFederationDevServerOptions } from '../../executors/module-federation-dev-server/schema'; -import type { StaticRemotesConfig } from '@nx/module-federation/src/utils'; - -export async function buildStaticRemotes( - staticRemotesConfig: StaticRemotesConfig, - nxBin, - context: ExecutorContext, - options: ModuleFederationDevServerOptions -) { - if (!staticRemotesConfig.remotes.length) { - return; - } - logger.info( - `NX Building ${staticRemotesConfig.remotes.length} static remotes...` - ); - const mappedLocationOfRemotes: Record = {}; - - for (const app of staticRemotesConfig.remotes) { - mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${ - options.host - }:${options.staticRemotesPort}/${ - staticRemotesConfig.config[app].urlSegment - }`; - } - - await new Promise((res, rej) => { - const staticProcess = fork( - nxBin, - [ - 'run-many', - `--target=build`, - `--projects=${staticRemotesConfig.remotes.join(',')}`, - ...(context.configurationName - ? [`--configuration=${context.configurationName}`] - : []), - ...(options.parallel ? [`--parallel=${options.parallel}`] : []), - ], - { - cwd: context.root, - stdio: ['ignore', 'pipe', 'pipe', 'ipc'], - } - ); - - // File to debug build failures e.g. 2024-01-01T00_00_0_0Z-build.log' - const remoteBuildLogFile = join( - workspaceDataDirectory, - // eslint-disable-next-line - `${new Date().toISOString().replace(/[:\.]/g, '_')}-build.log` - ); - const stdoutStream = createWriteStream(remoteBuildLogFile); - - staticProcess.stdout.on('data', (data) => { - const ANSII_CODE_REGEX = - // eslint-disable-next-line no-control-regex - /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; - const stdoutString = data.toString().replace(ANSII_CODE_REGEX, ''); - stdoutStream.write(stdoutString); - - // in addition to writing into the stdout stream, also show error directly in console - // so the error is easily discoverable. 'ERROR in' is the key word to search in webpack output. - if (stdoutString.includes('ERROR in')) { - logger.log(stdoutString); - } - - if (stdoutString.includes('Successfully ran target build')) { - staticProcess.stdout.removeAllListeners('data'); - logger.info( - `NX Built ${staticRemotesConfig.remotes.length} static remotes` - ); - res(); - } - }); - staticProcess.stderr.on('data', (data) => logger.info(data.toString())); - staticProcess.once('exit', (code) => { - stdoutStream.end(); - staticProcess.stdout.removeAllListeners('data'); - staticProcess.stderr.removeAllListeners('data'); - if (code !== 0) { - rej( - `Remote failed to start. A complete log can be found in: ${remoteBuildLogFile}` - ); - } else { - res(); - } - }); - process.on('SIGTERM', () => staticProcess.kill('SIGTERM')); - process.on('exit', () => staticProcess.kill('SIGTERM')); - }); - - return mappedLocationOfRemotes; -}