Skip to content

Commit

Permalink
feat(rspack): add module-federation-static-server (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 authored Sep 6, 2024
1 parent 8641d3f commit bfe7d2c
Show file tree
Hide file tree
Showing 7 changed files with 525 additions and 110 deletions.
5 changes: 5 additions & 0 deletions packages/rspack/executors.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
"implementation": "./src/executors/module-federation-ssr-dev-server/module-federation-ssr-dev-server.impl",
"schema": "./src/executors/module-federation-ssr-dev-server/schema.json",
"description": "Serve a host application along with it's known remotes."
},
"module-federation-static-server": {
"implementation": "./src/executors/module-federation-static-server/module-federation-static-server.impl",
"schema": "./src/executors/module-federation-static-server/schema.json",
"description": "Serve a host and its remotes statically."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,20 @@ import {
} from '@nx/devkit/src/utils/async-iterable';
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
import { cpSync, createWriteStream, existsSync } from 'fs';
import { fork } from 'node:child_process';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';
import { cpSync, existsSync } from 'fs';
import { extname, join } from 'path';
import {
getModuleFederationConfig,
getRemotes,
} from '../../utils/module-federation';
import { buildStaticRemotes } from '../../utils/module-federation/build-static.remotes';
import {
parseStaticRemotesConfig,
type StaticRemotesConfig,
} from '../../utils/module-federation/parse-static-remotes-config';
import { startRemoteProxies } from '../../utils/module-federation/start-remote-proxies';
import devServerExecutor from '../dev-server/dev-server.impl';
import { DevServerExecutorSchema } from '../dev-server/schema';

type ModuleFederationDevServerOptions = DevServerExecutorSchema & {
// Module Federation Specific Options
devRemotes?: (
| string
| {
remoteName: string;
configuration: string;
}
)[];
skipRemotes?: string[];
static?: boolean;
isInitialHost?: boolean;
parallel?: number;
staticRemotesPort?: number;
pathToManifestFile?: string;
};
import { ModuleFederationDevServerOptions } from './schema';

function getBuildOptions(buildTarget: string, context: ExecutorContext) {
const target = parseTargetString(buildTarget, context);
Expand Down Expand Up @@ -171,95 +153,6 @@ async function startRemotes(
return remoteIters;
}

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;
}

export default async function* moduleFederationDevServer(
options: ModuleFederationDevServerOptions,
context: ExecutorContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DevServerExecutorSchema } from '../dev-server/schema';

export type ModuleFederationDevServerOptions = DevServerExecutorSchema & {
// Module Federation Specific Options
devRemotes?: (
| string
| {
remoteName: string;
configuration: string;
}
)[];
skipRemotes?: string[];
static?: boolean;
isInitialHost?: boolean;
parallel?: number;
staticRemotesPort?: number;
pathToManifestFile?: string;
};
Loading

0 comments on commit bfe7d2c

Please sign in to comment.