Skip to content

Commit

Permalink
fix(module-federation): map static remote locations correctly (#21709)
Browse files Browse the repository at this point in the history
  • Loading branch information
leosvelperez committed Feb 8, 2024
1 parent a0e4cf7 commit 7f4c97e
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 79 deletions.
4 changes: 2 additions & 2 deletions e2e/angular-module-federation/src/module-federation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ describe('Angular Module Federation', () => {

if (runE2ETests()) {
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e --no-watch --verbose`,
`e2e ${host}-e2e --no-watch`,
(output) => output.includes('All specs passed!')
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
Expand Down Expand Up @@ -470,7 +470,7 @@ describe('Angular Module Federation', () => {

if (runE2ETests()) {
const e2eProcess = await runCommandUntil(
`e2e ${host}-e2e --no-watch --verbose`,
`e2e ${host}-e2e --no-watch`,
(output) => output.includes('All specs passed!')
);
await killProcessAndPorts(e2eProcess.pid, hostPort, hostPort + 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,39 @@
import { type Schema } from '../schema';
import { logger, type ExecutorContext } from '@nx/devkit';
import { fork } from 'child_process';
import type { StaticRemotesConfig } from './parse-static-remotes-config';

export async function buildStaticRemotes(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
staticRemotesConfig: StaticRemotesConfig,
nxBin,
context: ExecutorContext,
options: Schema
) {
if (
!remotes.staticRemotes ||
(Array.isArray(remotes.staticRemotes) && !remotes.staticRemotes.length)
) {
if (!staticRemotesConfig.remotes.length) {
return;
}
const mappedLocationOfRemotes: Record<string, string> = {};
for (const app of remotes.staticRemotes) {
for (const app of staticRemotesConfig.remotes) {
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
options.host
}:${options.staticRemotesPort}/${app}`;
}:${options.staticRemotesPort}/${
staticRemotesConfig.config[app].urlSegment
}`;
}
process.env.NX_MF_DEV_SERVER_STATIC_REMOTES = JSON.stringify(
mappedLocationOfRemotes
);

await new Promise<void>((res) => {
logger.info(
`NX Building ${remotes.staticRemotes.length} static remotes...`
`NX Building ${staticRemotesConfig.remotes.length} static remotes...`
);
const staticProcess = fork(
nxBin,
[
'run-many',
`--target=build`,
`--projects=${remotes.staticRemotes.join(',')}`,
`--projects=${staticRemotesConfig.remotes.join(',')}`,
...(context.configurationName
? [`--configuration=${context.configurationName}`]
: []),
Expand All @@ -54,7 +50,9 @@ export async function buildStaticRemotes(
const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
if (stdoutString.includes('Successfully ran target build')) {
staticProcess.stdout.removeAllListeners('data');
logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
logger.info(
`NX Built ${staticRemotesConfig.remotes.length} static remotes`
);
res();
}
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './build-static-remotes';
export * from './normalize-options';
export * from './parse-static-remotes-config';
export * from './start-dev-remotes';
export * from './start-static-remotes-file-server';
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { ExecutorContext } from '@nx/devkit';
import { basename, dirname } from 'path';

export type StaticRemoteConfig = {
basePath: string;
outputPath: string;
urlSegment: string;
};

export type StaticRemotesConfig = {
remotes: string[];
config: Record<string, StaticRemoteConfig> | undefined;
};

export function parseStaticRemotesConfig(
staticRemotes: string[] | undefined,
context: ExecutorContext
): StaticRemotesConfig {
if (!staticRemotes?.length) {
return { remotes: [], config: undefined };
}

const config: Record<string, StaticRemoteConfig> = {};
for (const app of staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
const basePath = dirname(outputPath);
const urlSegment = basename(outputPath);
config[app] = { basePath, outputPath, urlSegment };
}

return { remotes: staticRemotes, config };
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,39 @@
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 { dirname, join } from 'path';
import { join } from 'path';
import { cpSync } from 'fs';
import type { StaticRemotesConfig } from './parse-static-remotes-config';

export function startStaticRemotesFileServer(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
staticRemotesConfig: StaticRemotesConfig,
context: ExecutorContext,
options: Schema
) {
let shouldMoveToCommonLocation = false;
let commonOutputDirectory: string;
for (const app of remotes.staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
const directoryOfOutputPath = dirname(outputPath);

for (const app of staticRemotesConfig.remotes) {
const remoteBasePath = staticRemotesConfig.config[app].basePath;
if (!commonOutputDirectory) {
commonOutputDirectory = directoryOfOutputPath;
} else if (
commonOutputDirectory !== directoryOfOutputPath ||
!outputPath.endsWith(app)
) {
commonOutputDirectory = remoteBasePath;
} else if (commonOutputDirectory !== remoteBasePath) {
shouldMoveToCommonLocation = true;
break;
}
}

if (shouldMoveToCommonLocation) {
commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes');
for (const app of remotes.staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options
.outputPath;
cpSync(outputPath, join(commonOutputDirectory, app), {
force: true,
recursive: true,
});
for (const app of staticRemotesConfig.remotes) {
const remoteConfig = staticRemotesConfig.config[app];
cpSync(
remoteConfig.outputPath,
join(commonOutputDirectory, remoteConfig.urlSegment),
{
force: true,
recursive: true,
}
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { type Schema } from './schema';
import {
buildStaticRemotes,
normalizeOptions,
parseStaticRemotesConfig,
startDevRemotes,
startStaticRemotesFileServer,
} from './lib';
Expand Down Expand Up @@ -131,7 +132,11 @@ export async function* moduleFederationDevServerExecutor(
}, options.staticRemotesPort);
}

await buildStaticRemotes(remotes, nxBin, context, options);
const staticRemotesConfig = parseStaticRemotesConfig(
remotes.staticRemotes,
context
);
await buildStaticRemotes(staticRemotesConfig, nxBin, context, options);

const devRemoteIters = await startDevRemotes(
remotes,
Expand All @@ -142,7 +147,7 @@ export async function* moduleFederationDevServerExecutor(

const staticRemotesIter =
remotes.staticRemotes.length > 0
? startStaticRemotesFileServer(remotes, context, options)
? startStaticRemotesFileServer(staticRemotesConfig, context, options)
: undefined;

const removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '@nx/devkit/src/utils/async-iterable';
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
import { fork } from 'child_process';
import { dirname, join } from 'path';
import { basename, dirname, join } from 'path';
import { cpSync } from 'fs';

type ModuleFederationDevServerOptions = WebDevServerOptions & {
Expand All @@ -42,41 +42,34 @@ function getBuildOptions(buildTarget: string, context: ExecutorContext) {
}

function startStaticRemotesFileServer(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
staticRemotesConfig: StaticRemotesConfig,
context: ExecutorContext,
options: ModuleFederationDevServerOptions
) {
let shouldMoveToCommonLocation = false;
let commonOutputDirectory: string;
for (const app of remotes.staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
const directoryOfOutputPath = dirname(outputPath);

for (const app of staticRemotesConfig.remotes) {
const remoteBasePath = staticRemotesConfig.config[app].basePath;
if (!commonOutputDirectory) {
commonOutputDirectory = directoryOfOutputPath;
} else if (
commonOutputDirectory !== directoryOfOutputPath ||
!outputPath.endsWith(app)
) {
commonOutputDirectory = remoteBasePath;
} else if (commonOutputDirectory !== remoteBasePath) {
shouldMoveToCommonLocation = true;
break;
}
}

if (shouldMoveToCommonLocation) {
commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes');
for (const app of remotes.staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options
.outputPath;
cpSync(outputPath, join(commonOutputDirectory, app), {
force: true,
recursive: true,
});
for (const app of staticRemotesConfig.remotes) {
const remoteConfig = staticRemotesConfig.config[app];
cpSync(
remoteConfig.outputPath,
join(commonOutputDirectory, remoteConfig.urlSegment),
{
force: true,
recursive: true,
}
);
}
}

Expand Down Expand Up @@ -138,25 +131,25 @@ async function startDevRemotes(
}

async function buildStaticRemotes(
remotes: {
remotePorts: any[];
staticRemotes: string[];
devRemotes: string[];
},
staticRemotesConfig: StaticRemotesConfig,
nxBin,
context: ExecutorContext,
options: ModuleFederationDevServerOptions
) {
if (!remotes.staticRemotes.length) {
if (!staticRemotesConfig.remotes.length) {
return;
}
logger.info(`NX Building ${remotes.staticRemotes.length} static remotes...`);
logger.info(
`NX Building ${staticRemotesConfig.remotes.length} static remotes...`
);
const mappedLocationOfRemotes: Record<string, string> = {};

for (const app of remotes.staticRemotes) {
for (const app of staticRemotesConfig.remotes) {
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
options.host
}:${options.staticRemotesPort}/${app}`;
}:${options.staticRemotesPort}/${
staticRemotesConfig.config[app].urlSegment
}`;
}

process.env.NX_MF_DEV_SERVER_STATIC_REMOTES = JSON.stringify(
Expand All @@ -169,7 +162,7 @@ async function buildStaticRemotes(
[
'run-many',
`--target=build`,
`--projects=${remotes.staticRemotes.join(',')}`,
`--projects=${staticRemotesConfig.remotes.join(',')}`,
...(context.configurationName
? [`--configuration=${context.configurationName}`]
: []),
Expand All @@ -186,7 +179,9 @@ async function buildStaticRemotes(
const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
if (stdoutString.includes('Successfully ran target build')) {
staticProcess.stdout.removeAllListeners('data');
logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
logger.info(
`NX Built ${staticRemotesConfig.remotes.length} static remotes`
);
res();
}
});
Expand All @@ -201,6 +196,37 @@ async function buildStaticRemotes(
});
}

type StaticRemoteConfig = {
basePath: string;
outputPath: string;
urlSegment: string;
};

type StaticRemotesConfig = {
remotes: string[];
config: Record<string, StaticRemoteConfig> | undefined;
};

export function parseStaticRemotesConfig(
staticRemotes: string[] | undefined,
context: ExecutorContext
): StaticRemotesConfig {
if (!staticRemotes?.length) {
return { remotes: [], config: undefined };
}

const config: Record<string, StaticRemoteConfig> = {};
for (const app of staticRemotes) {
const outputPath =
context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
const basePath = dirname(outputPath);
const urlSegment = basename(outputPath);
config[app] = { basePath, outputPath, urlSegment };
}

return { remotes: staticRemotes, config };
}

export default async function* moduleFederationDevServer(
options: ModuleFederationDevServerOptions,
context: ExecutorContext
Expand Down Expand Up @@ -259,13 +285,17 @@ export default async function* moduleFederationDevServer(
}, options.staticRemotesPort);
}

await buildStaticRemotes(remotes, nxBin, context, options);
const staticRemotesConfig = parseStaticRemotesConfig(
remotes.staticRemotes,
context
);
await buildStaticRemotes(staticRemotesConfig, nxBin, context, options);

const devRemoteIters = await startDevRemotes(remotes, context);

const staticRemotesIter =
remotes.staticRemotes.length > 0
? startStaticRemotesFileServer(remotes, context, options)
? startStaticRemotesFileServer(staticRemotesConfig, context, options)
: undefined;

return yield* combineAsyncIterables(
Expand Down

0 comments on commit 7f4c97e

Please sign in to comment.