Skip to content

Commit

Permalink
feat(module-federation): use nx run-many to build static remotes in p…
Browse files Browse the repository at this point in the history
…arallel (#19987)
  • Loading branch information
Coly010 authored Nov 6, 2023
1 parent 33ca596 commit 1338a7c
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
"default": true,
"x-priority": "internal"
},
"parallel": {
"type": "number",
"description": "Max number of parallel processes for building static remotes"
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
"default": true,
"x-priority": "internal"
},
"parallel": {
"type": "number",
"description": "Max number of parallel processes for building static remotes"
}
},
"presets": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,106 +121,152 @@ export function executeModuleFederationDevServerBuilder(
pathToManifestFile
);

let isCollectingStaticRemoteOutput = true;

for (const app of remotes.staticRemotes) {
const remoteProjectServeTarget =
projectGraph.nodes[app].data.targets['serve-static'];
const isUsingModuleFederationDevServerExecutor =
remoteProjectServeTarget.executor.includes(
'module-federation-dev-server'
);
let outWithErr: null | string[] = [];
const staticRemoteBuildPromise = new Promise<void>((res) => {
logger.info(
`NX Building ${remotes.staticRemotes.length} static remotes...`
);
const staticProcess = fork(
nxBin,
[
'run',
`${app}:serve-static${
context.target.configuration ? `:${context.target.configuration}` : ''
}`,
...(isUsingModuleFederationDevServerExecutor
? [`--isInitialHost=false`]
'run-many',
`--target=build`,
`--projects=${remotes.staticRemotes.join(',')}`,
...(context.target.configuration
? [`--configuration=${context.target.configuration}`]
: []),
...(options.parallel ? [`--parallel=${options.parallel}`] : []),
],
{
cwd: context.workspaceRoot,
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
staticProcess.stdout.on('data', (data) => {
if (isCollectingStaticRemoteOutput) {
outWithErr.push(data.toString());
} else {
outWithErr = null;
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, '');
if (stdoutString.includes('Successfully ran target build')) {
staticProcess.stdout.removeAllListeners('data');
logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
res();
}
});
staticProcess.stderr.on('data', (data) => logger.info(data.toString()));
staticProcess.on('exit', (code) => {
if (code !== 0) {
logger.info(outWithErr.join(''));
throw new Error(`Remote failed to start. See above for errors.`);
throw new Error(`Remotes failed to build. See above for errors.`);
}
});
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
process.on('exit', () => staticProcess.kill('SIGTERM'));
}
});

const devRemotes$ = [];
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) {
throw new Error(
`Could not find executor for "serve" target in "${app}" project.`
);
}
return from(staticRemoteBuildPromise).pipe(
concatMap(() => {
let isCollectingStaticRemoteOutput = true;

const runOptions: { verbose?: boolean; isInitialHost?: boolean } = {};
const [collection, executor] =
workspaceProjects[app].targets['serve'].executor.split(':');
const isUsingModuleFederationDevServerExecutor = executor.includes(
'module-federation-dev-server'
);
const { schema } = getExecutorInformation(
collection,
executor,
workspaceRoot
);
if (
(options.verbose && schema.additionalProperties) ||
'verbose' in schema.properties
) {
runOptions.verbose = options.verbose;
}
for (const app of remotes.staticRemotes) {
const remoteProjectServeTarget =
projectGraph.nodes[app].data.targets['serve-static'];
const isUsingModuleFederationDevServerExecutor =
remoteProjectServeTarget.executor.includes(
'module-federation-dev-server'
);
let outWithErr: null | string[] = [];
const staticProcess = fork(
nxBin,
[
'run',
`${app}:serve-static${
context.target.configuration
? `:${context.target.configuration}`
: ''
}`,
...(isUsingModuleFederationDevServerExecutor
? [`--isInitialHost=false`]
: []),
],
{
cwd: context.workspaceRoot,
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
staticProcess.stdout.on('data', (data) => {
if (isCollectingStaticRemoteOutput) {
outWithErr.push(data.toString());
} else {
outWithErr = null;
staticProcess.stdout.removeAllListeners('data');
}
});
staticProcess.stderr.on('data', (data) => logger.info(data.toString()));
staticProcess.on('exit', (code) => {
if (code !== 0) {
logger.info(outWithErr.join(''));
throw new Error(`Remote failed to start. See above for errors.`);
}
});
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
process.on('exit', () => staticProcess.kill('SIGTERM'));
}

if (isUsingModuleFederationDevServerExecutor) {
runOptions.isInitialHost = false;
}
const devRemotes$ = [];
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) {
throw new Error(
`Could not find executor for "serve" target in "${app}" project.`
);
}

const serve$ = scheduleTarget(
context.workspaceRoot,
{
project: app,
target: 'serve',
configuration: context.target.configuration,
runOptions,
},
options.verbose
).then((obs) => {
obs.toPromise().catch((err) => {
throw new Error(
`Remote '${app}' failed to serve correctly due to the following: \r\n${err.toString()}`
const runOptions: { verbose?: boolean; isInitialHost?: boolean } = {};
const [collection, executor] =
workspaceProjects[app].targets['serve'].executor.split(':');
const isUsingModuleFederationDevServerExecutor = executor.includes(
'module-federation-dev-server'
);
});
});
const { schema } = getExecutorInformation(
collection,
executor,
workspaceRoot
);
if (
(options.verbose && schema.additionalProperties) ||
'verbose' in schema.properties
) {
runOptions.verbose = options.verbose;
}

devRemotes$.push(serve$);
}
if (isUsingModuleFederationDevServerExecutor) {
runOptions.isInitialHost = false;
}

const serve$ = scheduleTarget(
context.workspaceRoot,
{
project: app,
target: 'serve',
configuration: context.target.configuration,
runOptions,
},
options.verbose
).then((obs) => {
obs.toPromise().catch((err) => {
throw new Error(
`Remote '${app}' failed to serve correctly due to the following: \r\n${err.toString()}`
);
});
});

return devRemotes$.length > 0
? combineLatest([...devRemotes$]).pipe(concatMap(() => currExecutor))
: currExecutor;
devRemotes$.push(serve$);
}

return devRemotes$.length > 0
? combineLatest([...devRemotes$]).pipe(concatMap(() => currExecutor))
: currExecutor;
})
);
}

export default require('@angular-devkit/architect').createBuilder(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ export interface Schema {
pathToManifestFile?: string;
static?: boolean;
isInitialHost?: boolean;
parallel?: number;
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
"default": true,
"x-priority": "internal"
},
"parallel": {
"type": "number",
"description": "Max number of parallel processes for building static remotes"
}
},
"additionalProperties": false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type ModuleFederationDevServerOptions = WebDevServerOptions & {
skipRemotes?: string[];
static?: boolean;
isInitialHost?: boolean;
parallel?: number;
};

function getBuildOptions(buildTarget: string, context: ExecutorContext) {
Expand Down Expand Up @@ -80,6 +81,44 @@ export default async function* moduleFederationDevServer(
}
);

logger.info(`NX Building ${remotes.staticRemotes.length} static remotes...`);
await new Promise<void>((res) => {
const staticProcess = fork(
nxBin,
[
'run-many',
`--target=build`,
`--projects=${remotes.staticRemotes.join(',')}`,
...(context.configurationName
? [`--configuration=${context.configurationName}`]
: []),
...(options.parallel ? [`--parallel=${options.parallel}`] : []),
],
{
cwd: context.root,
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
}
);
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, '');
if (stdoutString.includes('Successfully ran target build')) {
staticProcess.stdout.removeAllListeners('data');
logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
res();
}
});
staticProcess.stderr.on('data', (data) => logger.info(data.toString()));
staticProcess.on('exit', (code) => {
if (code !== 0) {
throw new Error(`Remote failed to start. See above for errors.`);
}
});
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
process.on('exit', () => staticProcess.kill('SIGTERM'));
});

let isCollectingStaticRemoteOutput = true;
const devRemoteIters: AsyncIterable<{ success: boolean }>[] = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
"description": "Whether the host that is running this executor is the first in the project tree to do so.",
"default": true,
"x-priority": "internal"
},
"parallel": {
"type": "number",
"description": "Max number of parallel processes for building static remotes"
}
}
}

1 comment on commit 1338a7c

@vercel
Copy link

@vercel vercel bot commented on 1338a7c Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

nx-dev – ./

nx-dev-nrwl.vercel.app
nx-dev-git-master-nrwl.vercel.app
nx-five.vercel.app
nx.dev

Please sign in to comment.