Skip to content

Commit

Permalink
fix(react): serve dynamic remotes statically in their own processes
Browse files Browse the repository at this point in the history
  • Loading branch information
Coly010 committed Apr 5, 2024
1 parent 09b94b9 commit 3c8d463
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 25 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 @@ -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."
}
}
}

0 comments on commit 3c8d463

Please sign in to comment.