Skip to content

Commit

Permalink
fix(module-federation): serve dynamic remotes statically in their own…
Browse files Browse the repository at this point in the history
… processes (#22688)

(cherry picked from commit 640c61d)
  • Loading branch information
Coly010 authored and FrozenPandaz committed Apr 5, 2024
1 parent e4c4697 commit 99ed18a
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@
"staticRemotesPort": {
"type": "number",
"description": "The port at which to serve the file-server for the static remotes."
},
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
}
},
"presets": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,41 +5,38 @@ import {
runExecutor,
} from '@nx/devkit';

export async function startDevRemotes(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
export async function startRemotes(
remotes: string[],
workspaceProjects: Record<string, ProjectConfiguration>,
options: Schema,
context: ExecutorContext
context: ExecutorContext,
target: 'serve' | 'serve-static' = 'serve'
) {
const devRemotesIters: AsyncIterable<{ success: boolean }>[] = [];
for (const app of remotes.devRemotes) {
if (!workspaceProjects[app].targets?.['serve']) {
throw new Error(`Could not find "serve" target in "${app}" project.`);
} else if (!workspaceProjects[app].targets?.['serve'].executor) {
const remoteIters: AsyncIterable<{ success: boolean }>[] = [];
for (const app of remotes) {
if (!workspaceProjects[app].targets?.[target]) {
throw new Error(`Could not find "${target}" target in "${app}" project.`);
} else if (!workspaceProjects[app].targets?.[target].executor) {
throw new Error(
`Could not find executor for "serve" target in "${app}" project.`
`Could not find executor for "${target}" target in "${app}" project.`
);
}

const [collection, executor] =
workspaceProjects[app].targets['serve'].executor.split(':');
workspaceProjects[app].targets[target].executor.split(':');
const isUsingModuleFederationDevServerExecutor = executor.includes(
'module-federation-dev-server'
);

devRemotesIters.push(
remoteIters.push(
await runExecutor(
{
project: app,
target: 'serve',
target,
configuration: context.configurationName,
},
{
verbose: options.verbose ?? false,
...(target === 'serve' ? { verbose: options.verbose ?? false } : {}),
...(isUsingModuleFederationDevServerExecutor
? { isInitialHost: false }
: {}),
Expand All @@ -48,5 +45,5 @@ export async function startDevRemotes(
)
);
}
return devRemotesIters;
return remoteIters;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
buildStaticRemotes,
normalizeOptions,
parseStaticRemotesConfig,
startDevRemotes,
startRemotes,
startStaticRemotesFileServer,
} from './lib';
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
Expand Down Expand Up @@ -138,11 +138,20 @@ export async function* moduleFederationDevServerExecutor(
);
await buildStaticRemotes(staticRemotesConfig, nxBin, context, options);

const devRemoteIters = await startDevRemotes(
remotes,
const devRemoteIters = await startRemotes(
remotes.devRemotes,
workspaceProjects,
options,
context
context,
'serve'
);

const dynamicRemoteIters = await startRemotes(
remotes.dynamicRemotes,
workspaceProjects,
options,
context,
'serve-static'
);

const staticRemotesIter =
Expand All @@ -159,6 +168,7 @@ export async function* moduleFederationDevServerExecutor(
return yield* combineAsyncIterables(
removeBaseUrlEmission(currIter),
...devRemoteIters.map(removeBaseUrlEmission),
...dynamicRemoteIters.map(removeBaseUrlEmission),
...(staticRemotesIter ? [removeBaseUrlEmission(staticRemotesIter)] : []),
createAsyncIterable<{ success: true; baseUrl: string }>(
async ({ next, done }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { projectGraphCacheDirectory } from 'nx/src/utils/cache-directory';
import { fork } from 'node:child_process';
import { basename, dirname, join } from 'node:path';
import { createWriteStream, cpSync } from 'node:fs';
import { existsSync } from 'fs';
import { extname } from 'path';

type ModuleFederationDevServerOptions = WebDevServerOptions & {
devRemotes?: string[];
Expand All @@ -30,6 +32,7 @@ type ModuleFederationDevServerOptions = WebDevServerOptions & {
isInitialHost?: boolean;
parallel?: number;
staticRemotesPort?: number;
pathToManifestFile?: string;
};

function getBuildOptions(buildTarget: string, context: ExecutorContext) {
Expand Down Expand Up @@ -93,47 +96,48 @@ function startStaticRemotesFileServer(
return staticRemotesIter;
}

async function startDevRemotes(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
async function startRemotes(
remotes: string[],
context: ExecutorContext,
options: ModuleFederationDevServerOptions
options: ModuleFederationDevServerOptions,
target: 'serve' | 'serve-static' = 'serve'
) {
const devRemoteIters: AsyncIterable<{ success: boolean }>[] = [];
const remoteIters: AsyncIterable<{ success: boolean }>[] = [];

for (const app of remotes.devRemotes) {
for (const app of remotes) {
const remoteProjectServeTarget =
context.projectGraph.nodes[app].data.targets['serve'];
context.projectGraph.nodes[app].data.targets[target];
const isUsingModuleFederationDevServerExecutor =
remoteProjectServeTarget.executor.includes(
'module-federation-dev-server'
);

devRemoteIters.push(
const overrides =
target === 'serve'
? {
watch: true,
...(options.host ? { host: options.host } : {}),
...(options.ssl ? { ssl: options.ssl } : {}),
...(options.sslCert ? { sslCert: options.sslCert } : {}),
...(options.sslKey ? { sslKey: options.sslKey } : {}),
...(isUsingModuleFederationDevServerExecutor
? { isInitialHost: false }
: {}),
}
: {};
remoteIters.push(
await runExecutor(
{
project: app,
target: 'serve',
target,
configuration: context.configurationName,
},
{
watch: true,
...(options.host ? { host: options.host } : {}),
...(options.ssl ? { ssl: options.ssl } : {}),
...(options.sslCert ? { sslCert: options.sslCert } : {}),
...(options.sslKey ? { sslKey: options.sslKey } : {}),
...(isUsingModuleFederationDevServerExecutor
? { isInitialHost: false }
: {}),
},
overrides,
context
)
);
}
return devRemoteIters;
return remoteIters;
}

async function buildStaticRemotes(
Expand Down Expand Up @@ -269,6 +273,29 @@ export default async function* moduleFederationDevServer(
const p = context.projectsConfigurations.projects[context.projectName];
const buildOptions = getBuildOptions(options.buildTarget, context);

let pathToManifestFile = join(
context.root,
p.sourceRoot,
'assets/module-federation.manifest.json'
);
if (options.pathToManifestFile) {
const userPathToManifestFile = join(
context.root,
options.pathToManifestFile
);
if (!existsSync(userPathToManifestFile)) {
throw new Error(
`The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".`
);
} else if (extname(options.pathToManifestFile) !== '.json') {
throw new Error(
`The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.`
);
}

pathToManifestFile = userPathToManifestFile;
}

if (!options.isInitialHost) {
return yield* currIter;
}
Expand All @@ -288,7 +315,8 @@ export default async function* moduleFederationDevServer(
projectName: context.projectName,
projectGraph: context.projectGraph,
root: context.root,
}
},
pathToManifestFile
);

if (remotes.devRemotes.length > 0 && !initialStaticRemotesPorts) {
Expand All @@ -309,7 +337,18 @@ export default async function* moduleFederationDevServer(
);
await buildStaticRemotes(staticRemotesConfig, nxBin, context, options);

const devRemoteIters = await startDevRemotes(remotes, context, options);
const devRemoteIters = await startRemotes(
remotes.devRemotes,
context,
options,
'serve'
);
const dynamicRemotesIters = await startRemotes(
remotes.dynamicRemotes,
context,
options,
'serve-static'
);

const staticRemotesIter =
remotes.staticRemotes.length > 0
Expand All @@ -319,6 +358,7 @@ export default async function* moduleFederationDevServer(
return yield* combineAsyncIterables(
currIter,
...devRemoteIters,
...dynamicRemotesIters,
...(staticRemotesIter ? [staticRemotesIter] : []),
createAsyncIterable<{ success: true; baseUrl: string }>(
async ({ next, done }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@
"staticRemotesPort": {
"type": "number",
"description": "The port at which to serve the file-server for the static remotes."
},
"pathToManifestFile": {
"type": "string",
"description": "Path to a Module Federation manifest file (e.g. `my/path/to/module-federation.manifest.json`) containing the dynamic remote applications relative to the workspace root."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ function extractRemoteProjectsFromConfig(
pathToManifestFile?: string
) {
const remotes = [];
const dynamicRemotes = [];
if (pathToManifestFile && existsSync(pathToManifestFile)) {
const moduleFederationManifestJson = readFileSync(
pathToManifestFile,
Expand All @@ -35,14 +36,14 @@ function extractRemoteProjectsFromConfig(
typeof key === 'string' && typeof parsedManifest[key] === 'string'
)
) {
remotes.push(...Object.keys(parsedManifest));
dynamicRemotes.push(...Object.keys(parsedManifest));
}
}
}
const staticRemotes =
config.remotes?.map((r) => (Array.isArray(r) ? r[0] : r)) ?? [];
remotes.push(...staticRemotes);
return remotes;
return { remotes, dynamicRemotes };
}

function collectRemoteProjects(
Expand All @@ -64,7 +65,7 @@ function collectRemoteProjects(
context.root,
remoteProjectRoot
);
const remoteProjectRemotes =
const { remotes: remoteProjectRemotes } =
extractRemoteProjectsFromConfig(remoteProjectConfig);

remoteProjectRemotes.forEach((r) =>
Expand All @@ -80,7 +81,10 @@ export function getRemotes(
pathToManifestFile?: string
) {
const collectedRemotes = new Set<string>();
const remotes = extractRemoteProjectsFromConfig(config, pathToManifestFile);
const { remotes, dynamicRemotes } = extractRemoteProjectsFromConfig(
config,
pathToManifestFile
);
remotes.forEach((r) => collectRemoteProjects(r, collectedRemotes, context));
const remotesToSkip = new Set(
findMatchingProjects(skipRemotes, context.projectGraph.nodes) ?? []
Expand All @@ -98,10 +102,14 @@ export function getRemotes(
(r) => !remotesToSkip.has(r)
);

const knownDynamicRemotes = dynamicRemotes.filter(
(r) => !remotesToSkip.has(r)
);

logger.info(
`NX Starting module federation dev-server for ${chalk.bold(
context.projectName
)} with ${knownRemotes.length} remotes`
)} with ${[...knownRemotes, ...knownDynamicRemotes].length} remotes`
);

const devServeApps = new Set(
Expand All @@ -113,14 +121,20 @@ export function getRemotes(
);

const staticRemotes = knownRemotes.filter((r) => !devServeApps.has(r));
const devServeRemotes = knownRemotes.filter((r) => devServeApps.has(r));
const remotePorts = devServeRemotes.map(
const devServeRemotes = [...knownRemotes, ...dynamicRemotes].filter((r) =>
devServeApps.has(r)
);
const staticDynamicRemotes = knownDynamicRemotes.filter(
(r) => !devServeApps.has(r)
);
const remotePorts = [...devServeRemotes, ...staticDynamicRemotes].map(
(r) => context.projectGraph.nodes[r].data.targets['serve'].options.port
);

return {
staticRemotes,
devRemotes: devServeRemotes,
dynamicRemotes: staticDynamicRemotes,
remotePorts,
};
}
Expand Down

0 comments on commit 99ed18a

Please sign in to comment.