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`.
This commit is contained in:
parent
da901dec08
commit
5448046f06
@ -96,7 +96,6 @@ rust-toolchain @nrwl/nx-native-reviewers
|
|||||||
/packages/webpack/** @nrwl/nx-js-reviewers
|
/packages/webpack/** @nrwl/nx-js-reviewers
|
||||||
/e2e/webpack/** @nrwl/nx-js-reviewers
|
/e2e/webpack/** @nrwl/nx-js-reviewers
|
||||||
/packages/rspack/** @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
|
/e2e/rspack/** @nrwl/nx-js-reviewers
|
||||||
/packages/esbuild/** @nrwl/nx-js-reviewers
|
/packages/esbuild/** @nrwl/nx-js-reviewers
|
||||||
/e2e/esbuild/** @nrwl/nx-js-reviewers
|
/e2e/esbuild/** @nrwl/nx-js-reviewers
|
||||||
|
|||||||
@ -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<string, string> = {};
|
|
||||||
for (const app of staticRemotesConfig.remotes) {
|
|
||||||
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${
|
|
||||||
staticRemotesConfig.config[app].urlSegment
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((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;
|
|
||||||
}
|
|
||||||
@ -1,4 +1,2 @@
|
|||||||
export * from './build-static-remotes';
|
|
||||||
export * from './normalize-options';
|
export * from './normalize-options';
|
||||||
export * from './start-dev-remotes';
|
export * from './start-dev-remotes';
|
||||||
export * from './start-static-remotes-file-server';
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ export function normalizeOptions(schema: Schema): NormalizedSchema {
|
|||||||
liveReload: schema.liveReload ?? true,
|
liveReload: schema.liveReload ?? true,
|
||||||
open: schema.open ?? false,
|
open: schema.open ?? false,
|
||||||
ssl: schema.ssl ?? false,
|
ssl: schema.ssl ?? false,
|
||||||
|
verbose: schema.verbose ?? false,
|
||||||
sslCert: schema.sslCert ? join(workspaceRoot, schema.sslCert) : undefined,
|
sslCert: schema.sslCert ? join(workspaceRoot, schema.sslCert) : undefined,
|
||||||
sslKey: schema.sslKey ? join(workspaceRoot, schema.sslKey) : undefined,
|
sslKey: schema.sslKey ? join(workspaceRoot, schema.sslKey) : undefined,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
export async function startRemotes(
|
export async function startRemotes(
|
||||||
remotes: string[],
|
remotes: string[],
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>,
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
options: Schema,
|
options: Pick<Schema, 'devRemotes' | 'verbose'>,
|
||||||
context: ExecutorContext,
|
context: ExecutorContext,
|
||||||
target: 'serve' | 'serve-static' = 'serve'
|
target: 'serve' | 'serve-static' = 'serve'
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -4,24 +4,14 @@ import {
|
|||||||
readProjectsConfigurationFromProjectGraph,
|
readProjectsConfigurationFromProjectGraph,
|
||||||
} from '@nx/devkit';
|
} from '@nx/devkit';
|
||||||
import { type Schema } from './schema';
|
import { type Schema } from './schema';
|
||||||
import {
|
import { normalizeOptions, startRemotes } from './lib';
|
||||||
buildStaticRemotes,
|
|
||||||
normalizeOptions,
|
|
||||||
startRemotes,
|
|
||||||
startStaticRemotesFileServer,
|
|
||||||
} from './lib';
|
|
||||||
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
||||||
import {
|
import {
|
||||||
combineAsyncIterables,
|
combineAsyncIterables,
|
||||||
createAsyncIterable,
|
createAsyncIterable,
|
||||||
mapAsyncIterable,
|
mapAsyncIterable,
|
||||||
} from '@nx/devkit/src/utils/async-iterable';
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
import {
|
import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
startRemoteProxies,
|
|
||||||
parseStaticRemotesConfig,
|
|
||||||
} from '@nx/module-federation/src/utils';
|
|
||||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||||
@ -37,8 +27,6 @@ export async function* moduleFederationDevServerExecutor(
|
|||||||
schema: Schema,
|
schema: Schema,
|
||||||
context: ExecutorContext
|
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 options = normalizeOptions(schema);
|
||||||
|
|
||||||
const { projects: workspaceProjects } =
|
const { projects: workspaceProjects } =
|
||||||
@ -101,76 +89,14 @@ export async function* moduleFederationDevServerExecutor(
|
|||||||
|
|
||||||
validateDevRemotes(options, workspaceProjects);
|
validateDevRemotes(options, workspaceProjects);
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { remotes, staticRemotesIter, devRemoteIters } =
|
||||||
project.targets.build.options.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
project.root,
|
context,
|
||||||
'angular'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'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 removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
|
const removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
|
||||||
mapAsyncIterable(iter, (v) => ({
|
mapAsyncIterable(iter, (v) => ({
|
||||||
|
|||||||
@ -43,4 +43,5 @@ export type NormalizedSchema = SchemaWithBuildTarget & {
|
|||||||
liveReload: boolean;
|
liveReload: boolean;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
ssl: boolean;
|
ssl: boolean;
|
||||||
|
verbose: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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<string, string> = {};
|
|
||||||
for (const app of staticRemotesConfig.remotes) {
|
|
||||||
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${
|
|
||||||
staticRemotesConfig.config[app].urlSegment
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((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;
|
|
||||||
}
|
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { workspaceRoot } from '@nx/devkit';
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
import type { Schema } from '../schema';
|
import type { NormalizedSchema, Schema } from '../schema';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
export function normalizeOptions(options: Schema): Schema {
|
export function normalizeOptions(options: Schema): NormalizedSchema {
|
||||||
const devServeRemotes = !options.devRemotes
|
const devServeRemotes = !options.devRemotes
|
||||||
? []
|
? []
|
||||||
: Array.isArray(options.devRemotes)
|
: Array.isArray(options.devRemotes)
|
||||||
@ -12,6 +12,7 @@ export function normalizeOptions(options: Schema): Schema {
|
|||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
devRemotes: devServeRemotes,
|
devRemotes: devServeRemotes,
|
||||||
|
verbose: options.verbose ?? false,
|
||||||
ssl: options.ssl ?? false,
|
ssl: options.ssl ?? false,
|
||||||
sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined,
|
sslCert: options.sslCert ? join(workspaceRoot, options.sslCert) : undefined,
|
||||||
sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined,
|
sslKey: options.sslKey ? join(workspaceRoot, options.sslKey) : undefined,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
export async function startRemotes(
|
export async function startRemotes(
|
||||||
remotes: string[],
|
remotes: string[],
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>,
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
options: Schema,
|
options: Pick<Schema, 'devRemotes' | 'verbose'>,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
) {
|
) {
|
||||||
const target = 'serve-ssr';
|
const target = 'serve-ssr';
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,37 +1,29 @@
|
|||||||
import { executeSSRDevServerBuilder } from '@angular-devkit/build-angular';
|
import { executeSSRDevServerBuilder } from '@angular-devkit/build-angular';
|
||||||
import { type ExecutorContext, logger } from '@nx/devkit';
|
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 { existsSync } from 'fs';
|
||||||
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
|
||||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||||
import { extname, join } from 'path';
|
import { extname, join } from 'path';
|
||||||
import {
|
import {
|
||||||
getDynamicMfManifestFile,
|
getDynamicMfManifestFile,
|
||||||
validateDevRemotes,
|
validateDevRemotes,
|
||||||
} from '../../builders/utilities/module-federation';
|
} 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 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(
|
export async function* moduleFederationSsrDevServerExecutor(
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
) {
|
) {
|
||||||
const nxBin = require.resolve('nx/bin/nx');
|
|
||||||
const options = normalizeOptions(schema);
|
const options = normalizeOptions(schema);
|
||||||
|
|
||||||
const currIter = eachValueFrom(
|
const currIter = eachValueFrom(
|
||||||
@ -79,73 +71,15 @@ export async function* moduleFederationSsrDevServerExecutor(
|
|||||||
|
|
||||||
validateDevRemotes({ devRemotes: options.devRemotes }, workspaceProjects);
|
validateDevRemotes({ devRemotes: options.devRemotes }, workspaceProjects);
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { remotes, staticRemotesIter, devRemoteIters } =
|
||||||
project.targets.build.options.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
project.root,
|
context,
|
||||||
'angular'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'angular',
|
||||||
const remoteNames = options.devRemotes.map((r) =>
|
true
|
||||||
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 removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
|
const removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
|
||||||
mapAsyncIterable(iter, (v) => ({
|
mapAsyncIterable(iter, (v) => ({
|
||||||
@ -154,8 +88,8 @@ export async function* moduleFederationSsrDevServerExecutor(
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const combined = combineAsyncIterables(
|
const combined = combineAsyncIterables(
|
||||||
removeBaseUrlEmission(staticRemotes),
|
removeBaseUrlEmission(staticRemotesIter),
|
||||||
...(devRemotes ? devRemotes.map(removeBaseUrlEmission) : []),
|
...(devRemoteIters ? devRemoteIters.map(removeBaseUrlEmission) : []),
|
||||||
createAsyncIterable<{ success: true; baseUrl: string }>(
|
createAsyncIterable<{ success: true; baseUrl: string }>(
|
||||||
async ({ next, done }) => {
|
async ({ next, done }) => {
|
||||||
if (!options.isInitialHost) {
|
if (!options.isInitialHost) {
|
||||||
@ -172,7 +106,7 @@ export async function* moduleFederationSsrDevServerExecutor(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const portsToWaitFor = staticRemotes
|
const portsToWaitFor = staticRemotesIter
|
||||||
? [options.staticRemotesPort, ...remotes.remotePorts]
|
? [options.staticRemotesPort, ...remotes.remotePorts]
|
||||||
: [...remotes.remotePorts];
|
: [...remotes.remotePorts];
|
||||||
await Promise.all(
|
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) {
|
for await (const result of combined) {
|
||||||
if (result.success === false) throw new Error('Remotes failed to start');
|
if (result.success === false) throw new Error('Remotes failed to start');
|
||||||
if (result.success) refs--;
|
if (result.success) refs--;
|
||||||
|
|||||||
@ -9,3 +9,9 @@ export interface Schema extends SSRDevServerBuilderOptions {
|
|||||||
staticRemotesPort?: number;
|
staticRemotesPort?: number;
|
||||||
isInitialHost?: boolean;
|
isInitialHost?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface NormalizedSchema extends Schema {
|
||||||
|
devRemotes: DevRemoteDefinition[];
|
||||||
|
ssl: boolean;
|
||||||
|
verbose: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
|
"@nx/web": "file:../web",
|
||||||
"picocolors": "^1.1.0",
|
"picocolors": "^1.1.0",
|
||||||
"webpack": "5.88.0",
|
"webpack": "5.88.0",
|
||||||
"@rspack/core": "1.1.3",
|
"@rspack/core": "1.1.3",
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
import { ExecutorContext, logger } from '@nx/devkit';
|
||||||
import { ExecutorContext } from '@nx/devkit';
|
import { type StaticRemotesConfig } from '../../utils';
|
||||||
import { ModuleFederationDevServerOptions } from '../executors/module-federation-dev-server/schema';
|
import { type BuildStaticRemotesOptions } from './models';
|
||||||
import { logger } from 'nx/src/utils/logger';
|
|
||||||
import { fork } from 'node:child_process';
|
import { fork } from 'node:child_process';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
||||||
@ -11,7 +10,8 @@ export async function buildStaticRemotes(
|
|||||||
staticRemotesConfig: StaticRemotesConfig,
|
staticRemotesConfig: StaticRemotesConfig,
|
||||||
nxBin,
|
nxBin,
|
||||||
context: ExecutorContext,
|
context: ExecutorContext,
|
||||||
options: ModuleFederationDevServerOptions
|
options: BuildStaticRemotesOptions,
|
||||||
|
buildTarget: 'build' | 'server' = 'build'
|
||||||
) {
|
) {
|
||||||
if (!staticRemotesConfig.remotes.length) {
|
if (!staticRemotesConfig.remotes.length) {
|
||||||
return;
|
return;
|
||||||
@ -34,7 +34,7 @@ export async function buildStaticRemotes(
|
|||||||
nxBin,
|
nxBin,
|
||||||
[
|
[
|
||||||
'run-many',
|
'run-many',
|
||||||
`--target=build`,
|
`--target=${buildTarget}`,
|
||||||
`--projects=${staticRemotesConfig.remotes.join(',')}`,
|
`--projects=${staticRemotesConfig.remotes.join(',')}`,
|
||||||
...(context.configurationName
|
...(context.configurationName
|
||||||
? [`--configuration=${context.configurationName}`]
|
? [`--configuration=${context.configurationName}`]
|
||||||
@ -66,7 +66,7 @@ export async function buildStaticRemotes(
|
|||||||
logger.log(stdoutString);
|
logger.log(stdoutString);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stdoutString.includes('Successfully ran target build')) {
|
if (stdoutString.includes(`Successfully ran target ${buildTarget}`)) {
|
||||||
staticProcess.stdout.removeAllListeners('data');
|
staticProcess.stdout.removeAllListeners('data');
|
||||||
logger.info(
|
logger.info(
|
||||||
`NX Built ${staticRemotesConfig.remotes.length} static remotes`
|
`NX Built ${staticRemotesConfig.remotes.length} static remotes`
|
||||||
4
packages/module-federation/src/executors/utils/index.ts
Normal file
4
packages/module-federation/src/executors/utils/index.ts
Normal file
@ -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';
|
||||||
38
packages/module-federation/src/executors/utils/models.ts
Normal file
38
packages/module-federation/src/executors/utils/models.ts
Normal file
@ -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<string, ProjectConfiguration>,
|
||||||
|
options: {
|
||||||
|
devRemotes: DevRemoteDefinition[];
|
||||||
|
verbose: boolean;
|
||||||
|
},
|
||||||
|
context: ExecutorContext,
|
||||||
|
target: 'serve' | 'serve-static'
|
||||||
|
) => Promise<AsyncIterable<{ success: boolean }>[]>;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import { type ExecutorContext, workspaceRoot } from '@nx/devkit';
|
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 fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { cpSync } from 'fs';
|
import { cpSync } from 'fs';
|
||||||
import type { StaticRemotesConfig } from '@nx/module-federation/src/utils';
|
import type { StaticRemotesConfig } from '../../utils';
|
||||||
|
|
||||||
export function startStaticRemotesFileServer(
|
export function startStaticRemotesFileServer(
|
||||||
staticRemotesConfig: StaticRemotesConfig,
|
staticRemotesConfig: StaticRemotesConfig,
|
||||||
context: ExecutorContext,
|
context: ExecutorContext,
|
||||||
options: Schema
|
options: StaticRemotesOptions,
|
||||||
|
forceMoveToCommonLocation = false
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
!staticRemotesConfig.remotes ||
|
!staticRemotesConfig.remotes ||
|
||||||
@ -16,15 +17,17 @@ export function startStaticRemotesFileServer(
|
|||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let shouldMoveToCommonLocation = false;
|
let shouldMoveToCommonLocation = forceMoveToCommonLocation || false;
|
||||||
let commonOutputDirectory: string;
|
let commonOutputDirectory: string;
|
||||||
for (const app of staticRemotesConfig.remotes) {
|
if (!forceMoveToCommonLocation) {
|
||||||
const remoteBasePath = staticRemotesConfig.config[app].basePath;
|
for (const app of staticRemotesConfig.remotes) {
|
||||||
if (!commonOutputDirectory) {
|
const remoteBasePath = staticRemotesConfig.config[app].basePath;
|
||||||
commonOutputDirectory = remoteBasePath;
|
if (!commonOutputDirectory) {
|
||||||
} else if (commonOutputDirectory !== remoteBasePath) {
|
commonOutputDirectory = remoteBasePath;
|
||||||
shouldMoveToCommonLocation = true;
|
} else if (commonOutputDirectory !== remoteBasePath) {
|
||||||
break;
|
shouldMoveToCommonLocation = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,3 +65,21 @@ export function startStaticRemotesFileServer(
|
|||||||
);
|
);
|
||||||
return staticRemotesIter;
|
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;
|
||||||
|
}
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
|
export * from './start-remotes';
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import { ExecutorContext, ProjectConfiguration, runExecutor } from '@nx/devkit';
|
||||||
|
import { NormalizedModuleFederationDevServerOptions } from '../schema';
|
||||||
|
|
||||||
|
export async function startRemotes(
|
||||||
|
remotes: string[],
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1,162 +1,22 @@
|
|||||||
import {
|
import { ExecutorContext, logger } from '@nx/devkit';
|
||||||
ExecutorContext,
|
|
||||||
logger,
|
|
||||||
parseTargetString,
|
|
||||||
readTargetOptions,
|
|
||||||
runExecutor,
|
|
||||||
workspaceRoot,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import devServerExecutor from '@nx/webpack/src/executors/dev-server/dev-server.impl';
|
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 fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||||
import { ModuleFederationDevServerOptions } from './schema';
|
import { ModuleFederationDevServerOptions } from './schema';
|
||||||
import {
|
import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
startRemoteProxies,
|
|
||||||
parseStaticRemotesConfig,
|
|
||||||
type StaticRemotesConfig,
|
|
||||||
} from '@nx/module-federation/src/utils';
|
|
||||||
import {
|
import {
|
||||||
combineAsyncIterables,
|
combineAsyncIterables,
|
||||||
createAsyncIterable,
|
createAsyncIterable,
|
||||||
} from '@nx/devkit/src/utils/async-iterable';
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
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 { extname, join } from 'path';
|
||||||
import { buildStaticRemotes } from '../../utils/build-static.remotes';
|
import { getBuildOptions, normalizeOptions, startRemotes } from './lib';
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function* moduleFederationDevServer(
|
export default async function* moduleFederationDevServer(
|
||||||
options: ModuleFederationDevServerOptions,
|
schema: ModuleFederationDevServerOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
||||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
const options = normalizeOptions(schema);
|
||||||
const nxBin = require.resolve('nx/bin/nx');
|
|
||||||
const currIter = options.static
|
const currIter = options.static
|
||||||
? fileServerExecutor(
|
? fileServerExecutor(
|
||||||
{
|
{
|
||||||
@ -201,74 +61,14 @@ export default async function* moduleFederationDevServer(
|
|||||||
return yield* currIter;
|
return yield* currIter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { staticRemotesIter, devRemoteIters, remotes } =
|
||||||
buildOptions.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
p.root,
|
context,
|
||||||
'react'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'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
|
|
||||||
);
|
|
||||||
|
|
||||||
return yield* combineAsyncIterables(
|
return yield* combineAsyncIterables(
|
||||||
currIter,
|
currIter,
|
||||||
|
|||||||
@ -1,17 +1,19 @@
|
|||||||
import { WebDevServerOptions } from '@nx/webpack';
|
import { WebDevServerOptions } from '@nx/webpack';
|
||||||
|
import { DevRemoteDefinition } from '@nx/module-federation/src/executors/utils';
|
||||||
|
|
||||||
export type ModuleFederationDevServerOptions = WebDevServerOptions & {
|
export type ModuleFederationDevServerOptions = WebDevServerOptions & {
|
||||||
devRemotes?: (
|
devRemotes?: DevRemoteDefinition[];
|
||||||
| string
|
|
||||||
| {
|
|
||||||
remoteName: string;
|
|
||||||
configuration: string;
|
|
||||||
}
|
|
||||||
)[];
|
|
||||||
skipRemotes?: string[];
|
skipRemotes?: string[];
|
||||||
static?: boolean;
|
static?: boolean;
|
||||||
isInitialHost?: boolean;
|
isInitialHost?: boolean;
|
||||||
parallel?: number;
|
parallel?: number;
|
||||||
staticRemotesPort?: number;
|
staticRemotesPort?: number;
|
||||||
pathToManifestFile?: string;
|
pathToManifestFile?: string;
|
||||||
|
verbose?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NormalizedModuleFederationDevServerOptions =
|
||||||
|
ModuleFederationDevServerOptions & {
|
||||||
|
devRemotes: DevRemoteDefinition[];
|
||||||
|
verbose: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
|
export * from './start-remotes';
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import { ModuleFederationSsrDevServerOptions } from '../schema';
|
||||||
|
import { runExecutor, ExecutorContext, ProjectConfiguration } from '@nx/devkit';
|
||||||
|
|
||||||
|
export async function startRemotes(
|
||||||
|
remotes: string[],
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1,257 +1,21 @@
|
|||||||
import {
|
import { ExecutorContext, logger } from '@nx/devkit';
|
||||||
ExecutorContext,
|
|
||||||
logger,
|
|
||||||
parseTargetString,
|
|
||||||
readTargetOptions,
|
|
||||||
runExecutor,
|
|
||||||
workspaceRoot,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import ssrDevServerExecutor from '@nx/webpack/src/executors/ssr-dev-server/ssr-dev-server.impl';
|
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 { extname, join } from 'path';
|
||||||
import {
|
import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
parseStaticSsrRemotesConfig,
|
|
||||||
type StaticRemotesConfig,
|
|
||||||
startSsrRemoteProxies,
|
|
||||||
} from '@nx/module-federation/src/utils';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
combineAsyncIterables,
|
combineAsyncIterables,
|
||||||
createAsyncIterable,
|
createAsyncIterable,
|
||||||
} from '@nx/devkit/src/utils/async-iterable';
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
import { fork } from 'child_process';
|
import { existsSync } from 'fs';
|
||||||
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 { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||||
|
import { ModuleFederationSsrDevServerOptions } from './schema';
|
||||||
type ModuleFederationSsrDevServerOptions = WebSsrDevServerOptions & {
|
import { getBuildOptions, normalizeOptions, startRemotes } from './lib';
|
||||||
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<string, string> = {};
|
|
||||||
|
|
||||||
for (const remoteApp of staticRemotesConfig.remotes) {
|
|
||||||
mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${
|
|
||||||
staticRemotesConfig.config[remoteApp].urlSegment
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function* moduleFederationSsrDevServer(
|
export default async function* moduleFederationSsrDevServer(
|
||||||
ssrDevServerOptions: ModuleFederationSsrDevServerOptions,
|
ssrDevServerOptions: ModuleFederationSsrDevServerOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
) {
|
) {
|
||||||
const options = normalizeOptions(ssrDevServerOptions);
|
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);
|
let iter: any = ssrDevServerExecutor(options, context);
|
||||||
const projectConfig =
|
const projectConfig =
|
||||||
context.projectsConfigurations.projects[context.projectName];
|
context.projectsConfigurations.projects[context.projectName];
|
||||||
@ -285,74 +49,15 @@ export default async function* moduleFederationSsrDevServer(
|
|||||||
return yield* iter;
|
return yield* iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { staticRemotesIter, devRemoteIters, remotes } =
|
||||||
buildOptions.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
projectConfig.root,
|
context,
|
||||||
'react'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'react',
|
||||||
const remoteNames = options.devRemotes?.map((remote) =>
|
true
|
||||||
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 combined = combineAsyncIterables(staticRemotesIter, ...devRemoteIters);
|
const combined = combineAsyncIterables(staticRemotesIter, ...devRemoteIters);
|
||||||
|
|
||||||
|
|||||||
29
packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts
vendored
Normal file
29
packages/react/src/executors/module-federation-ssr-dev-server/schema.d.ts
vendored
Normal file
@ -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;
|
||||||
|
};
|
||||||
@ -16,7 +16,7 @@ import {
|
|||||||
parseStaticRemotesConfig,
|
parseStaticRemotesConfig,
|
||||||
StaticRemotesConfig,
|
StaticRemotesConfig,
|
||||||
} from '@nx/module-federation/src/utils';
|
} 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 { fork } from 'child_process';
|
||||||
import type { WebpackExecutorOptions } from '@nx/webpack';
|
import type { WebpackExecutorOptions } from '@nx/webpack';
|
||||||
import * as process from 'node:process';
|
import * as process from 'node:process';
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
|
export * from './start-remotes';
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
import { ModuleFederationDevServerOptions } from '../schema';
|
||||||
|
import { ProjectConfiguration, ExecutorContext, runExecutor } from '@nx/devkit';
|
||||||
|
|
||||||
|
export async function startRemotes(
|
||||||
|
remotes: string[],
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1,162 +1,22 @@
|
|||||||
import {
|
import { ExecutorContext, logger } from '@nx/devkit';
|
||||||
ExecutorContext,
|
|
||||||
logger,
|
|
||||||
parseTargetString,
|
|
||||||
readTargetOptions,
|
|
||||||
runExecutor,
|
|
||||||
workspaceRoot,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import {
|
import {
|
||||||
combineAsyncIterables,
|
combineAsyncIterables,
|
||||||
createAsyncIterable,
|
createAsyncIterable,
|
||||||
} from '@nx/devkit/src/utils/async-iterable';
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
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 { extname, join } from 'path';
|
||||||
import {
|
import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
parseStaticRemotesConfig,
|
|
||||||
type StaticRemotesConfig,
|
|
||||||
startRemoteProxies,
|
|
||||||
} from '@nx/module-federation/src/utils';
|
|
||||||
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
|
|
||||||
import devServerExecutor from '../dev-server/dev-server.impl';
|
import devServerExecutor from '../dev-server/dev-server.impl';
|
||||||
import { ModuleFederationDevServerOptions } from './schema';
|
import { ModuleFederationDevServerOptions } from './schema';
|
||||||
|
import { getBuildOptions, normalizeOptions, startRemotes } from './lib';
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function* moduleFederationDevServer(
|
export default async function* moduleFederationDevServer(
|
||||||
options: ModuleFederationDevServerOptions,
|
schema: ModuleFederationDevServerOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
): AsyncIterableIterator<{ success: boolean; baseUrl?: string }> {
|
||||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
const options = normalizeOptions(schema);
|
||||||
const nxBin = require.resolve('nx/bin/nx');
|
|
||||||
const currIter = options.static
|
const currIter = options.static
|
||||||
? fileServerExecutor(
|
? fileServerExecutor(
|
||||||
{
|
{
|
||||||
@ -201,74 +61,14 @@ export default async function* moduleFederationDevServer(
|
|||||||
return yield* currIter;
|
return yield* currIter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { staticRemotesIter, devRemoteIters, remotes } =
|
||||||
buildOptions.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
p.root,
|
context,
|
||||||
'react'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'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
|
|
||||||
);
|
|
||||||
|
|
||||||
return yield* combineAsyncIterables(
|
return yield* combineAsyncIterables(
|
||||||
currIter,
|
currIter,
|
||||||
|
|||||||
@ -15,4 +15,11 @@ export type ModuleFederationDevServerOptions = DevServerExecutorSchema & {
|
|||||||
parallel?: number;
|
parallel?: number;
|
||||||
staticRemotesPort?: number;
|
staticRemotesPort?: number;
|
||||||
pathToManifestFile?: string;
|
pathToManifestFile?: string;
|
||||||
|
verbose?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type NormalizedModuleFederationDevServerOptions =
|
||||||
|
ModuleFederationDevServerOptions & {
|
||||||
|
devRemotes: DevServerExecutorSchema['devRemotes'];
|
||||||
|
verbose: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
export * from './normalize-options';
|
||||||
|
export * from './start-remotes';
|
||||||
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
import { ModuleFederationSsrDevServerOptions } from '../schema';
|
||||||
|
import { runExecutor, ExecutorContext, ProjectConfiguration } from '@nx/devkit';
|
||||||
|
|
||||||
|
export async function startRemotes(
|
||||||
|
remotes: string[],
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -1,256 +1,22 @@
|
|||||||
import {
|
import { ExecutorContext, logger } from '@nx/devkit';
|
||||||
ExecutorContext,
|
|
||||||
logger,
|
|
||||||
parseTargetString,
|
|
||||||
readTargetOptions,
|
|
||||||
runExecutor,
|
|
||||||
workspaceRoot,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { extname, join } from 'path';
|
import { extname, join } from 'path';
|
||||||
import {
|
import { startRemoteIterators } from '@nx/module-federation/src/executors/utils';
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
parseStaticSsrRemotesConfig,
|
|
||||||
type StaticRemotesConfig,
|
|
||||||
startSsrRemoteProxies,
|
|
||||||
} from '@nx/module-federation/src/utils';
|
|
||||||
import { RspackSsrDevServerOptions } from '../ssr-dev-server/schema';
|
|
||||||
import ssrDevServerExecutor from '../ssr-dev-server/ssr-dev-server.impl';
|
import ssrDevServerExecutor from '../ssr-dev-server/ssr-dev-server.impl';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
combineAsyncIterables,
|
combineAsyncIterables,
|
||||||
createAsyncIterable,
|
createAsyncIterable,
|
||||||
} from '@nx/devkit/src/utils/async-iterable';
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
import { fork } from 'child_process';
|
import { existsSync } from 'fs';
|
||||||
import { cpSync, createWriteStream, existsSync } from 'fs';
|
|
||||||
|
|
||||||
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
|
||||||
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||||
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
|
import { ModuleFederationSsrDevServerOptions } from './schema';
|
||||||
|
import { getBuildOptions, normalizeOptions, startRemotes } from './lib';
|
||||||
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<string, string> = {};
|
|
||||||
|
|
||||||
for (const remoteApp of staticRemotesConfig.remotes) {
|
|
||||||
mapLocationOfRemotes[remoteApp] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${
|
|
||||||
staticRemotesConfig.config[remoteApp].urlSegment
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function* moduleFederationSsrDevServer(
|
export default async function* moduleFederationSsrDevServer(
|
||||||
ssrDevServerOptions: ModuleFederationSsrDevServerOptions,
|
ssrDevServerOptions: ModuleFederationSsrDevServerOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
) {
|
) {
|
||||||
const options = normalizeOptions(ssrDevServerOptions);
|
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 iter = ssrDevServerExecutor(options, context);
|
||||||
const projectConfig =
|
const projectConfig =
|
||||||
context.projectsConfigurations.projects[context.projectName];
|
context.projectsConfigurations.projects[context.projectName];
|
||||||
@ -284,74 +50,15 @@ export default async function* moduleFederationSsrDevServer(
|
|||||||
return yield* iter;
|
return yield* iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
const { staticRemotesIter, devRemoteIters, remotes } =
|
||||||
buildOptions.tsConfig,
|
await startRemoteIterators(
|
||||||
context.root,
|
options,
|
||||||
projectConfig.root,
|
context,
|
||||||
'react'
|
startRemotes,
|
||||||
);
|
pathToManifestFile,
|
||||||
|
'react',
|
||||||
const remoteNames = options.devRemotes?.map((remote) =>
|
true
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
return yield* combineAsyncIterables(
|
return yield* combineAsyncIterables(
|
||||||
iter,
|
iter,
|
||||||
|
|||||||
29
packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts
vendored
Normal file
29
packages/rspack/src/executors/module-federation-ssr-dev-server/schema.d.ts
vendored
Normal file
@ -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;
|
||||||
|
};
|
||||||
@ -22,7 +22,7 @@ import {
|
|||||||
parseStaticRemotesConfig,
|
parseStaticRemotesConfig,
|
||||||
StaticRemotesConfig,
|
StaticRemotesConfig,
|
||||||
} from '@nx/module-federation/src/utils';
|
} 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 { ModuleFederationDevServerOptions } from '../module-federation-dev-server/schema';
|
||||||
import type { RspackExecutorSchema } from '../rspack/schema';
|
import type { RspackExecutorSchema } from '../rspack/schema';
|
||||||
import { ModuleFederationStaticServerSchema } from './schema';
|
import { ModuleFederationStaticServerSchema } from './schema';
|
||||||
|
|||||||
@ -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<string, string> = {};
|
|
||||||
|
|
||||||
for (const app of staticRemotesConfig.remotes) {
|
|
||||||
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${
|
|
||||||
staticRemotesConfig.config[app].urlSegment
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
await new Promise<void>((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;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user