feat(angular): convert module-federation-dev-server to executor (#20252)
This commit is contained in:
parent
afafb79494
commit
d22e860269
@ -6536,6 +6536,14 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "module-federation-dev-server",
|
||||||
|
"path": "/nx-api/angular/executors/module-federation-dev-server",
|
||||||
|
"name": "module-federation-dev-server",
|
||||||
|
"children": [],
|
||||||
|
"isExternal": false,
|
||||||
|
"disableCollapsible": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "webpack-browser",
|
"id": "webpack-browser",
|
||||||
"path": "/nx-api/angular/executors/webpack-browser",
|
"path": "/nx-api/angular/executors/webpack-browser",
|
||||||
@ -6560,14 +6568,6 @@
|
|||||||
"isExternal": false,
|
"isExternal": false,
|
||||||
"disableCollapsible": false
|
"disableCollapsible": false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "module-federation-dev-server",
|
|
||||||
"path": "/nx-api/angular/executors/module-federation-dev-server",
|
|
||||||
"name": "module-federation-dev-server",
|
|
||||||
"children": [],
|
|
||||||
"isExternal": false,
|
|
||||||
"disableCollapsible": false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "module-federation-dev-ssr",
|
"id": "module-federation-dev-ssr",
|
||||||
"path": "/nx-api/angular/executors/module-federation-dev-ssr",
|
"path": "/nx-api/angular/executors/module-federation-dev-ssr",
|
||||||
|
|||||||
@ -67,6 +67,15 @@
|
|||||||
"path": "/nx-api/angular/executors/browser-esbuild",
|
"path": "/nx-api/angular/executors/browser-esbuild",
|
||||||
"type": "executor"
|
"type": "executor"
|
||||||
},
|
},
|
||||||
|
"/nx-api/angular/executors/module-federation-dev-server": {
|
||||||
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
||||||
|
"file": "generated/packages/angular/executors/module-federation-dev-server.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "module-federation-dev-server",
|
||||||
|
"originalFilePath": "/packages/angular/src/executors/module-federation-dev-server/schema.json",
|
||||||
|
"path": "/nx-api/angular/executors/module-federation-dev-server",
|
||||||
|
"type": "executor"
|
||||||
|
},
|
||||||
"/nx-api/angular/executors/webpack-browser": {
|
"/nx-api/angular/executors/webpack-browser": {
|
||||||
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
|
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
|
||||||
"file": "generated/packages/angular/executors/webpack-browser.json",
|
"file": "generated/packages/angular/executors/webpack-browser.json",
|
||||||
@ -94,15 +103,6 @@
|
|||||||
"path": "/nx-api/angular/executors/webpack-server",
|
"path": "/nx-api/angular/executors/webpack-server",
|
||||||
"type": "executor"
|
"type": "executor"
|
||||||
},
|
},
|
||||||
"/nx-api/angular/executors/module-federation-dev-server": {
|
|
||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
|
||||||
"file": "generated/packages/angular/executors/module-federation-dev-server.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "module-federation-dev-server",
|
|
||||||
"originalFilePath": "/packages/angular/src/builders/module-federation-dev-server/schema.json",
|
|
||||||
"path": "/nx-api/angular/executors/module-federation-dev-server",
|
|
||||||
"type": "executor"
|
|
||||||
},
|
|
||||||
"/nx-api/angular/executors/module-federation-dev-ssr": {
|
"/nx-api/angular/executors/module-federation-dev-ssr": {
|
||||||
"description": "The module-federation-dev-ssr executor is reserved exclusively for use with host Module Federation applications that use SSR. It allows the user to specify which remote applications should be served with the host.",
|
"description": "The module-federation-dev-ssr executor is reserved exclusively for use with host Module Federation applications that use SSR. It allows the user to specify which remote applications should be served with the host.",
|
||||||
"file": "generated/packages/angular/executors/module-federation-dev-ssr.json",
|
"file": "generated/packages/angular/executors/module-federation-dev-ssr.json",
|
||||||
|
|||||||
@ -62,6 +62,15 @@
|
|||||||
"path": "angular/executors/browser-esbuild",
|
"path": "angular/executors/browser-esbuild",
|
||||||
"type": "executor"
|
"type": "executor"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
||||||
|
"file": "generated/packages/angular/executors/module-federation-dev-server.json",
|
||||||
|
"hidden": false,
|
||||||
|
"name": "module-federation-dev-server",
|
||||||
|
"originalFilePath": "/packages/angular/src/executors/module-federation-dev-server/schema.json",
|
||||||
|
"path": "angular/executors/module-federation-dev-server",
|
||||||
|
"type": "executor"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
|
"description": "The `webpack-browser` executor is very similar to the standard `browser` builder provided by the Angular Devkit. It allows you to build your Angular application to a build artifact that can be hosted online. There are some key differences: \n- Supports Custom Webpack Configurations \n- Supports Incremental Building",
|
||||||
"file": "generated/packages/angular/executors/webpack-browser.json",
|
"file": "generated/packages/angular/executors/webpack-browser.json",
|
||||||
@ -89,15 +98,6 @@
|
|||||||
"path": "angular/executors/webpack-server",
|
"path": "angular/executors/webpack-server",
|
||||||
"type": "executor"
|
"type": "executor"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
|
||||||
"file": "generated/packages/angular/executors/module-federation-dev-server.json",
|
|
||||||
"hidden": false,
|
|
||||||
"name": "module-federation-dev-server",
|
|
||||||
"originalFilePath": "/packages/angular/src/builders/module-federation-dev-server/schema.json",
|
|
||||||
"path": "angular/executors/module-federation-dev-server",
|
|
||||||
"type": "executor"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "The module-federation-dev-ssr executor is reserved exclusively for use with host Module Federation applications that use SSR. It allows the user to specify which remote applications should be served with the host.",
|
"description": "The module-federation-dev-ssr executor is reserved exclusively for use with host Module Federation applications that use SSR. It allows the user to specify which remote applications should be served with the host.",
|
||||||
"file": "generated/packages/angular/executors/module-federation-dev-ssr.json",
|
"file": "generated/packages/angular/executors/module-federation-dev-ssr.json",
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "module-federation-dev-server",
|
"name": "module-federation-dev-server",
|
||||||
"implementation": "/packages/angular/src/builders/module-federation-dev-server/module-federation-dev-server.impl.ts",
|
"implementation": "/packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts",
|
||||||
"schema": {
|
"schema": {
|
||||||
"version": 2,
|
|
||||||
"outputCapture": "direct-nodejs",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"title": "Schema for Module Federation Dev Server",
|
"title": "Schema for Module Federation Dev Server",
|
||||||
|
"outputCapture": "direct-nodejs",
|
||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"presets": [
|
"presets": [
|
||||||
@ -148,6 +147,6 @@
|
|||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"path": "/packages/angular/src/builders/module-federation-dev-server/schema.json",
|
"path": "/packages/angular/src/executors/module-federation-dev-server/schema.json",
|
||||||
"type": "executor"
|
"type": "executor"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -323,10 +323,10 @@
|
|||||||
- [ng-packagr-lite](/nx-api/angular/executors/ng-packagr-lite)
|
- [ng-packagr-lite](/nx-api/angular/executors/ng-packagr-lite)
|
||||||
- [package](/nx-api/angular/executors/package)
|
- [package](/nx-api/angular/executors/package)
|
||||||
- [browser-esbuild](/nx-api/angular/executors/browser-esbuild)
|
- [browser-esbuild](/nx-api/angular/executors/browser-esbuild)
|
||||||
|
- [module-federation-dev-server](/nx-api/angular/executors/module-federation-dev-server)
|
||||||
- [webpack-browser](/nx-api/angular/executors/webpack-browser)
|
- [webpack-browser](/nx-api/angular/executors/webpack-browser)
|
||||||
- [dev-server](/nx-api/angular/executors/dev-server)
|
- [dev-server](/nx-api/angular/executors/dev-server)
|
||||||
- [webpack-server](/nx-api/angular/executors/webpack-server)
|
- [webpack-server](/nx-api/angular/executors/webpack-server)
|
||||||
- [module-federation-dev-server](/nx-api/angular/executors/module-federation-dev-server)
|
|
||||||
- [module-federation-dev-ssr](/nx-api/angular/executors/module-federation-dev-ssr)
|
- [module-federation-dev-ssr](/nx-api/angular/executors/module-federation-dev-ssr)
|
||||||
- [generators](/nx-api/angular/generators)
|
- [generators](/nx-api/angular/generators)
|
||||||
- [add-linting](/nx-api/angular/generators/add-linting)
|
- [add-linting](/nx-api/angular/generators/add-linting)
|
||||||
|
|||||||
@ -19,6 +19,11 @@
|
|||||||
"implementation": "./src/executors/browser-esbuild/browser-esbuild.impl",
|
"implementation": "./src/executors/browser-esbuild/browser-esbuild.impl",
|
||||||
"schema": "./src/executors/browser-esbuild/schema.json",
|
"schema": "./src/executors/browser-esbuild/schema.json",
|
||||||
"description": "Builds your application with esbuild and adds support for incremental builds."
|
"description": "Builds your application with esbuild and adds support for incremental builds."
|
||||||
|
},
|
||||||
|
"module-federation-dev-server": {
|
||||||
|
"implementation": "./src/executors/module-federation-dev-server/module-federation-dev-server.impl",
|
||||||
|
"schema": "./src/executors/module-federation-dev-server/schema.json",
|
||||||
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"builders": {
|
"builders": {
|
||||||
@ -37,11 +42,6 @@
|
|||||||
"schema": "./src/builders/webpack-server/schema.json",
|
"schema": "./src/builders/webpack-server/schema.json",
|
||||||
"description": "The `webpack-server` executor is very similar to the standard `server` builder provided by the Angular Devkit. It is usually used in tandem with `@nrwl/angular:webpack-browser` when your Angular application uses a custom webpack configuration and NgUniversal for SSR."
|
"description": "The `webpack-server` executor is very similar to the standard `server` builder provided by the Angular Devkit. It is usually used in tandem with `@nrwl/angular:webpack-browser` when your Angular application uses a custom webpack configuration and NgUniversal for SSR."
|
||||||
},
|
},
|
||||||
"module-federation-dev-server": {
|
|
||||||
"implementation": "./src/builders/module-federation-dev-server/module-federation-dev-server.impl",
|
|
||||||
"schema": "./src/builders/module-federation-dev-server/schema.json",
|
|
||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host."
|
|
||||||
},
|
|
||||||
"module-federation-dev-ssr": {
|
"module-federation-dev-ssr": {
|
||||||
"implementation": "./src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl",
|
"implementation": "./src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl",
|
||||||
"schema": "./src/builders/module-federation-dev-ssr/schema.json",
|
"schema": "./src/builders/module-federation-dev-ssr/schema.json",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
export * from './src/builders/module-federation-dev-server/module-federation-dev-server.impl';
|
|
||||||
export * from './src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl';
|
export * from './src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl';
|
||||||
export * from './src/builders/webpack-browser/webpack-browser.impl';
|
export * from './src/builders/webpack-browser/webpack-browser.impl';
|
||||||
export * from './src/builders/webpack-server/webpack-server.impl';
|
export * from './src/builders/webpack-server/webpack-server.impl';
|
||||||
|
export * from './src/executors/module-federation-dev-server/module-federation-dev-server.impl';
|
||||||
export * from './src/executors/delegate-build/delegate-build.impl';
|
export * from './src/executors/delegate-build/delegate-build.impl';
|
||||||
export * from './src/executors/ng-packagr-lite/ng-packagr-lite.impl';
|
export * from './src/executors/ng-packagr-lite/ng-packagr-lite.impl';
|
||||||
export * from './src/executors/package/package.impl';
|
export * from './src/executors/package/package.impl';
|
||||||
|
|||||||
@ -1,405 +0,0 @@
|
|||||||
import type {
|
|
||||||
NormalizedSchema,
|
|
||||||
Schema,
|
|
||||||
SchemaWithBrowserTarget,
|
|
||||||
SchemaWithBuildTarget,
|
|
||||||
} from './schema';
|
|
||||||
import {
|
|
||||||
logger,
|
|
||||||
type ProjectConfiguration,
|
|
||||||
type ProjectGraph,
|
|
||||||
readCachedProjectGraph,
|
|
||||||
readNxJson,
|
|
||||||
workspaceRoot,
|
|
||||||
} from '@nx/devkit';
|
|
||||||
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
|
|
||||||
import { executeDevServerBuilder } from '../dev-server/dev-server.impl';
|
|
||||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
|
||||||
import { getExecutorInformation } from 'nx/src/command-line/run/executor-utils';
|
|
||||||
import { validateDevRemotes } from '../utilities/module-federation';
|
|
||||||
import { existsSync } from 'fs';
|
|
||||||
import { dirname, extname, join } from 'path';
|
|
||||||
import {
|
|
||||||
getModuleFederationConfig,
|
|
||||||
getRemotes,
|
|
||||||
} from '@nx/webpack/src/utils/module-federation';
|
|
||||||
import { fork } from 'child_process';
|
|
||||||
import { combineLatest, concatMap, from, switchMap } from 'rxjs';
|
|
||||||
import { cpSync } from 'fs';
|
|
||||||
|
|
||||||
function buildStaticRemotes(
|
|
||||||
remotes: {
|
|
||||||
remotePorts: any[];
|
|
||||||
staticRemotes: string[];
|
|
||||||
devRemotes: string[];
|
|
||||||
},
|
|
||||||
nxBin,
|
|
||||||
context: import('@angular-devkit/architect').BuilderContext,
|
|
||||||
options: Schema
|
|
||||||
) {
|
|
||||||
if (!remotes.staticRemotes.length) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const mappedLocationOfRemotes: Record<string, string> = {};
|
|
||||||
for (const app of remotes.staticRemotes) {
|
|
||||||
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
|
|
||||||
options.host
|
|
||||||
}:${options.staticRemotesPort}/${app}`;
|
|
||||||
}
|
|
||||||
process.env.NX_MF_DEV_SERVER_STATIC_REMOTES = JSON.stringify(
|
|
||||||
mappedLocationOfRemotes
|
|
||||||
);
|
|
||||||
|
|
||||||
const staticRemoteBuildPromise = new Promise<void>((res) => {
|
|
||||||
logger.info(
|
|
||||||
`NX Building ${remotes.staticRemotes.length} static remotes...`
|
|
||||||
);
|
|
||||||
const staticProcess = fork(
|
|
||||||
nxBin,
|
|
||||||
[
|
|
||||||
'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) => {
|
|
||||||
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(`Remotes failed to build. See above for errors.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
|
|
||||||
process.on('exit', () => staticProcess.kill('SIGTERM'));
|
|
||||||
});
|
|
||||||
return staticRemoteBuildPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startStaticRemotesFileServer(
|
|
||||||
remotes: {
|
|
||||||
remotePorts: any[];
|
|
||||||
staticRemotes: string[];
|
|
||||||
devRemotes: string[];
|
|
||||||
},
|
|
||||||
projectGraph: ProjectGraph,
|
|
||||||
options: Schema,
|
|
||||||
context: import('@angular-devkit/architect').BuilderContext
|
|
||||||
) {
|
|
||||||
let shouldMoveToCommonLocation = false;
|
|
||||||
let commonOutputDirectory: string;
|
|
||||||
|
|
||||||
for (const app of remotes.staticRemotes) {
|
|
||||||
const outputPath =
|
|
||||||
projectGraph.nodes[app].data.targets['build'].options.outputPath;
|
|
||||||
const directoryOfOutputPath = dirname(outputPath);
|
|
||||||
|
|
||||||
if (!commonOutputDirectory) {
|
|
||||||
commonOutputDirectory = directoryOfOutputPath;
|
|
||||||
} else if (
|
|
||||||
commonOutputDirectory !== directoryOfOutputPath ||
|
|
||||||
!outputPath.endsWith(app)
|
|
||||||
) {
|
|
||||||
shouldMoveToCommonLocation = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldMoveToCommonLocation) {
|
|
||||||
commonOutputDirectory = join(workspaceRoot, 'tmp/static-remotes');
|
|
||||||
for (const app of remotes.staticRemotes) {
|
|
||||||
const outputPath =
|
|
||||||
projectGraph.nodes[app].data.targets['build'].options.outputPath;
|
|
||||||
cpSync(outputPath, join(commonOutputDirectory, app), {
|
|
||||||
force: true,
|
|
||||||
recursive: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const staticRemotesIter$ = from(
|
|
||||||
import('@nx/web/src/executors/file-server/file-server.impl')
|
|
||||||
).pipe(
|
|
||||||
switchMap((fileServerExecutor) =>
|
|
||||||
fileServerExecutor.default(
|
|
||||||
{
|
|
||||||
cors: true,
|
|
||||||
watch: false,
|
|
||||||
staticFilePath: commonOutputDirectory,
|
|
||||||
parallel: false,
|
|
||||||
spa: false,
|
|
||||||
withDeps: false,
|
|
||||||
host: options.host,
|
|
||||||
port: options.staticRemotesPort,
|
|
||||||
ssl: options.ssl,
|
|
||||||
sslCert: options.sslCert,
|
|
||||||
sslKey: options.sslKey,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectGraph,
|
|
||||||
root: context.workspaceRoot,
|
|
||||||
target:
|
|
||||||
projectGraph.nodes[context.target.project].data.targets[
|
|
||||||
context.target.target
|
|
||||||
],
|
|
||||||
targetName: context.target.target,
|
|
||||||
projectName: context.target.project,
|
|
||||||
configurationName: context.target.configuration,
|
|
||||||
cwd: context.currentDirectory,
|
|
||||||
isVerbose: options.verbose,
|
|
||||||
projectsConfigurations:
|
|
||||||
readProjectsConfigurationFromProjectGraph(projectGraph),
|
|
||||||
nxJsonConfiguration: readNxJson(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return staticRemotesIter$;
|
|
||||||
}
|
|
||||||
|
|
||||||
function startDevRemotes(
|
|
||||||
remotes: {
|
|
||||||
remotePorts: any[];
|
|
||||||
staticRemotes: string[];
|
|
||||||
devRemotes: string[];
|
|
||||||
},
|
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>,
|
|
||||||
options: Schema,
|
|
||||||
context: import('@angular-devkit/architect').BuilderContext
|
|
||||||
) {
|
|
||||||
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 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,
|
|
||||||
workspaceProjects
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
(options.verbose && schema.additionalProperties) ||
|
|
||||||
'verbose' in schema.properties
|
|
||||||
) {
|
|
||||||
runOptions.verbose = options.verbose;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isUsingModuleFederationDevServerExecutor) {
|
|
||||||
runOptions.isInitialHost = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const serve$ = scheduleTarget(
|
|
||||||
context.workspaceRoot,
|
|
||||||
{
|
|
||||||
project: app,
|
|
||||||
target: 'serve',
|
|
||||||
configuration: context.target.configuration,
|
|
||||||
runOptions,
|
|
||||||
projects: workspaceProjects,
|
|
||||||
},
|
|
||||||
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()}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
devRemotes$.push(serve$);
|
|
||||||
}
|
|
||||||
return devRemotes$;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeModuleFederationDevServerBuilder(
|
|
||||||
schema: Schema,
|
|
||||||
context: import('@angular-devkit/architect').BuilderContext
|
|
||||||
): ReturnType<typeof executeDevServerBuilder | any> {
|
|
||||||
// Force Node to resolve to look for the nx binary that is inside node_modules
|
|
||||||
const nxBin = require.resolve('nx/bin/nx');
|
|
||||||
const options = normalizeOptions(schema);
|
|
||||||
options.staticRemotesPort ??= options.port + 1;
|
|
||||||
|
|
||||||
const projectGraph = readCachedProjectGraph();
|
|
||||||
const { projects: workspaceProjects } =
|
|
||||||
readProjectsConfigurationFromProjectGraph(projectGraph);
|
|
||||||
const project = workspaceProjects[context.target.project];
|
|
||||||
|
|
||||||
const staticFileServer = from(
|
|
||||||
import('@nx/web/src/executors/file-server/file-server.impl')
|
|
||||||
).pipe(
|
|
||||||
switchMap((fileServerExecutor) =>
|
|
||||||
fileServerExecutor.default(
|
|
||||||
{
|
|
||||||
port: options.port,
|
|
||||||
host: options.host,
|
|
||||||
ssl: options.ssl,
|
|
||||||
buildTarget: options.buildTarget,
|
|
||||||
parallel: false,
|
|
||||||
spa: false,
|
|
||||||
withDeps: false,
|
|
||||||
cors: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projectGraph,
|
|
||||||
root: context.workspaceRoot,
|
|
||||||
target:
|
|
||||||
projectGraph.nodes[context.target.project].data.targets[
|
|
||||||
context.target.target
|
|
||||||
],
|
|
||||||
targetName: context.target.target,
|
|
||||||
projectName: context.target.project,
|
|
||||||
configurationName: context.target.configuration,
|
|
||||||
cwd: context.currentDirectory,
|
|
||||||
isVerbose: options.verbose,
|
|
||||||
projectsConfigurations: { projects: workspaceProjects, version: 2 },
|
|
||||||
nxJsonConfiguration: readNxJson(),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const webpackDevServer = executeDevServerBuilder(options, context);
|
|
||||||
|
|
||||||
const currExecutor = options.static ? staticFileServer : webpackDevServer;
|
|
||||||
|
|
||||||
if (options.isInitialHost === false) {
|
|
||||||
return currExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pathToManifestFile = join(
|
|
||||||
context.workspaceRoot,
|
|
||||||
project.sourceRoot,
|
|
||||||
'assets/module-federation.manifest.json'
|
|
||||||
);
|
|
||||||
if (options.pathToManifestFile) {
|
|
||||||
const userPathToManifestFile = join(
|
|
||||||
context.workspaceRoot,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
validateDevRemotes(options, workspaceProjects);
|
|
||||||
|
|
||||||
const moduleFederationConfig = getModuleFederationConfig(
|
|
||||||
project.targets.build.options.tsConfig,
|
|
||||||
context.workspaceRoot,
|
|
||||||
project.root,
|
|
||||||
'angular'
|
|
||||||
);
|
|
||||||
|
|
||||||
const remotes = getRemotes(
|
|
||||||
options.devRemotes,
|
|
||||||
options.skipRemotes,
|
|
||||||
moduleFederationConfig,
|
|
||||||
{
|
|
||||||
projectName: project.name,
|
|
||||||
projectGraph,
|
|
||||||
root: context.workspaceRoot,
|
|
||||||
},
|
|
||||||
pathToManifestFile
|
|
||||||
);
|
|
||||||
|
|
||||||
if (remotes.devRemotes.length > 0 && !schema.staticRemotesPort) {
|
|
||||||
options.staticRemotesPort = options.devRemotes.reduce((portToUse, r) => {
|
|
||||||
const remotePort =
|
|
||||||
projectGraph.nodes[r].data.targets['serve'].options.port;
|
|
||||||
if (remotePort >= portToUse) {
|
|
||||||
return remotePort + 1;
|
|
||||||
}
|
|
||||||
}, options.staticRemotesPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
const staticRemoteBuildPromise = buildStaticRemotes(
|
|
||||||
remotes,
|
|
||||||
nxBin,
|
|
||||||
context,
|
|
||||||
options
|
|
||||||
);
|
|
||||||
|
|
||||||
return from(staticRemoteBuildPromise).pipe(
|
|
||||||
concatMap(() => {
|
|
||||||
const staticRemotesIter$ =
|
|
||||||
remotes.staticRemotes.length > 0
|
|
||||||
? startStaticRemotesFileServer(
|
|
||||||
remotes,
|
|
||||||
projectGraph,
|
|
||||||
options,
|
|
||||||
context
|
|
||||||
)
|
|
||||||
: from(Promise.resolve());
|
|
||||||
|
|
||||||
const devRemotes$ = startDevRemotes(
|
|
||||||
remotes,
|
|
||||||
workspaceProjects,
|
|
||||||
options,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
|
|
||||||
return devRemotes$.length > 0
|
|
||||||
? combineLatest([...devRemotes$, staticRemotesIter$]).pipe(
|
|
||||||
concatMap(() => currExecutor)
|
|
||||||
)
|
|
||||||
: from(staticRemotesIter$).pipe(concatMap(() => currExecutor));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default require('@angular-devkit/architect').createBuilder(
|
|
||||||
executeModuleFederationDevServerBuilder
|
|
||||||
);
|
|
||||||
|
|
||||||
function normalizeOptions(schema: Schema): NormalizedSchema {
|
|
||||||
let buildTarget = (schema as SchemaWithBuildTarget).buildTarget;
|
|
||||||
if ((schema as SchemaWithBrowserTarget).browserTarget) {
|
|
||||||
buildTarget ??= (schema as SchemaWithBrowserTarget).browserTarget;
|
|
||||||
delete (schema as SchemaWithBrowserTarget).browserTarget;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...schema,
|
|
||||||
buildTarget,
|
|
||||||
host: schema.host ?? 'localhost',
|
|
||||||
port: schema.port ?? 4200,
|
|
||||||
liveReload: schema.liveReload ?? true,
|
|
||||||
open: schema.open ?? false,
|
|
||||||
ssl: schema.ssl ?? false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -158,9 +158,8 @@ export function validateDevRemotes(
|
|||||||
options: { devRemotes?: string[] },
|
options: { devRemotes?: string[] },
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>
|
workspaceProjects: Record<string, ProjectConfiguration>
|
||||||
): void {
|
): void {
|
||||||
const invalidDevRemotes = options.devRemotes?.filter(
|
const invalidDevRemotes =
|
||||||
(remote) => !workspaceProjects[remote]
|
options.devRemotes?.filter((remote) => !workspaceProjects[remote]) ?? [];
|
||||||
);
|
|
||||||
|
|
||||||
if (invalidDevRemotes.length) {
|
if (invalidDevRemotes.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
import { type Schema } from '../schema';
|
||||||
|
import { logger, type ExecutorContext } from '@nx/devkit';
|
||||||
|
import { fork } from 'child_process';
|
||||||
|
|
||||||
|
export async function buildStaticRemotes(
|
||||||
|
remotes: {
|
||||||
|
remotePorts: any[];
|
||||||
|
staticRemotes: string[];
|
||||||
|
devRemotes: string[];
|
||||||
|
},
|
||||||
|
nxBin,
|
||||||
|
context: ExecutorContext,
|
||||||
|
options: Schema
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
!remotes.staticRemotes ||
|
||||||
|
(Array.isArray(remotes.staticRemotes) && !remotes.staticRemotes.length)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const mappedLocationOfRemotes: Record<string, string> = {};
|
||||||
|
for (const app of remotes.staticRemotes) {
|
||||||
|
mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${
|
||||||
|
options.host
|
||||||
|
}:${options.staticRemotesPort}/${app}`;
|
||||||
|
}
|
||||||
|
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...`
|
||||||
|
);
|
||||||
|
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(`Remotes failed to build. See above for errors.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
|
||||||
|
process.on('exit', () => staticProcess.kill('SIGTERM'));
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
export * from './build-static-remotes';
|
||||||
|
export * from './normalize-options';
|
||||||
|
export * from './start-dev-remotes';
|
||||||
|
export * from './start-static-remotes-file-server';
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
import type {
|
||||||
|
NormalizedSchema,
|
||||||
|
Schema,
|
||||||
|
SchemaWithBrowserTarget,
|
||||||
|
SchemaWithBuildTarget,
|
||||||
|
} from '../schema';
|
||||||
|
|
||||||
|
export function normalizeOptions(schema: Schema): NormalizedSchema {
|
||||||
|
let buildTarget = (schema as SchemaWithBuildTarget).buildTarget;
|
||||||
|
if ((schema as SchemaWithBrowserTarget).browserTarget) {
|
||||||
|
buildTarget ??= (schema as SchemaWithBrowserTarget).browserTarget;
|
||||||
|
delete (schema as SchemaWithBrowserTarget).browserTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...schema,
|
||||||
|
buildTarget,
|
||||||
|
host: schema.host ?? 'localhost',
|
||||||
|
port: schema.port ?? 4200,
|
||||||
|
liveReload: schema.liveReload ?? true,
|
||||||
|
open: schema.open ?? false,
|
||||||
|
ssl: schema.ssl ?? false,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
import { type Schema } from '../schema';
|
||||||
|
import {
|
||||||
|
type ExecutorContext,
|
||||||
|
type ProjectConfiguration,
|
||||||
|
runExecutor,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
|
export async function startDevRemotes(
|
||||||
|
remotes: {
|
||||||
|
remotePorts: any[];
|
||||||
|
staticRemotes: string[];
|
||||||
|
devRemotes: string[];
|
||||||
|
},
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
options: Schema,
|
||||||
|
context: ExecutorContext
|
||||||
|
) {
|
||||||
|
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) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find executor for "serve" target in "${app}" project.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [collection, executor] =
|
||||||
|
workspaceProjects[app].targets['serve'].executor.split(':');
|
||||||
|
const isUsingModuleFederationDevServerExecutor = executor.includes(
|
||||||
|
'module-federation-dev-server'
|
||||||
|
);
|
||||||
|
|
||||||
|
devRemotesIters.push(
|
||||||
|
await runExecutor(
|
||||||
|
{
|
||||||
|
project: app,
|
||||||
|
target: 'serve',
|
||||||
|
configuration: context.configurationName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
verbose: options.verbose ?? false,
|
||||||
|
...(isUsingModuleFederationDevServerExecutor
|
||||||
|
? { isInitialHost: false }
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
context
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return devRemotesIters;
|
||||||
|
}
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
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 { cpSync } from 'fs';
|
||||||
|
|
||||||
|
export function startStaticRemotesFileServer(
|
||||||
|
remotes: {
|
||||||
|
remotePorts: any[];
|
||||||
|
staticRemotes: string[];
|
||||||
|
devRemotes: string[];
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!commonOutputDirectory) {
|
||||||
|
commonOutputDirectory = directoryOfOutputPath;
|
||||||
|
} else if (
|
||||||
|
commonOutputDirectory !== directoryOfOutputPath ||
|
||||||
|
!outputPath.endsWith(app)
|
||||||
|
) {
|
||||||
|
shouldMoveToCommonLocation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const staticRemotesIter = fileServerExecutor(
|
||||||
|
{
|
||||||
|
cors: true,
|
||||||
|
watch: false,
|
||||||
|
staticFilePath: commonOutputDirectory,
|
||||||
|
parallel: false,
|
||||||
|
spa: false,
|
||||||
|
withDeps: false,
|
||||||
|
host: options.host,
|
||||||
|
port: options.staticRemotesPort,
|
||||||
|
ssl: options.ssl,
|
||||||
|
sslCert: options.sslCert,
|
||||||
|
sslKey: options.sslKey,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
);
|
||||||
|
return staticRemotesIter;
|
||||||
|
}
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
import {
|
||||||
|
type ExecutorContext,
|
||||||
|
logger,
|
||||||
|
readProjectsConfigurationFromProjectGraph,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { type Schema } from './schema';
|
||||||
|
import {
|
||||||
|
buildStaticRemotes,
|
||||||
|
normalizeOptions,
|
||||||
|
startDevRemotes,
|
||||||
|
startStaticRemotesFileServer,
|
||||||
|
} from './lib';
|
||||||
|
import { eachValueFrom } from '@nx/devkit/src/utils/rxjs-for-await';
|
||||||
|
import {
|
||||||
|
combineAsyncIterables,
|
||||||
|
createAsyncIterable,
|
||||||
|
mapAsyncIterable,
|
||||||
|
} from '@nx/devkit/src/utils/async-iterable';
|
||||||
|
import {
|
||||||
|
getModuleFederationConfig,
|
||||||
|
getRemotes,
|
||||||
|
} from '@nx/webpack/src/utils/module-federation';
|
||||||
|
import { waitForPortOpen } from '@nx/web/src/utils/wait-for-port-open';
|
||||||
|
import fileServerExecutor from '@nx/web/src/executors/file-server/file-server.impl';
|
||||||
|
import { createBuilderContext } from 'nx/src/adapter/ngcli-adapter';
|
||||||
|
import { executeDevServerBuilder } from '../../builders/dev-server/dev-server.impl';
|
||||||
|
import { validateDevRemotes } from '../../builders/utilities/module-federation';
|
||||||
|
import { extname, join } from 'path';
|
||||||
|
import { existsSync } from 'fs';
|
||||||
|
|
||||||
|
export default async function* moduleFederationDevServerExecutor(
|
||||||
|
schema: Schema,
|
||||||
|
context: ExecutorContext
|
||||||
|
) {
|
||||||
|
// Force Node to resolve to look for the nx binary that is inside node_modules
|
||||||
|
const nxBin = require.resolve('nx/bin/nx');
|
||||||
|
const options = normalizeOptions(schema);
|
||||||
|
options.staticRemotesPort ??= options.port + 1;
|
||||||
|
|
||||||
|
const { projects: workspaceProjects } =
|
||||||
|
readProjectsConfigurationFromProjectGraph(context.projectGraph);
|
||||||
|
const project = workspaceProjects[context.projectName];
|
||||||
|
|
||||||
|
const currIter = options.static
|
||||||
|
? fileServerExecutor(
|
||||||
|
{
|
||||||
|
port: options.port,
|
||||||
|
host: options.host,
|
||||||
|
ssl: options.ssl,
|
||||||
|
buildTarget: options.buildTarget,
|
||||||
|
parallel: false,
|
||||||
|
spa: false,
|
||||||
|
withDeps: false,
|
||||||
|
cors: true,
|
||||||
|
},
|
||||||
|
context
|
||||||
|
)
|
||||||
|
: eachValueFrom(
|
||||||
|
executeDevServerBuilder(
|
||||||
|
options,
|
||||||
|
await createBuilderContext(
|
||||||
|
{
|
||||||
|
builderName: '@nx/angular:webpack-browser',
|
||||||
|
description: 'Build a browser application',
|
||||||
|
optionSchema: await import(
|
||||||
|
'../../builders/webpack-browser/schema.json'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
context
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (options.isInitialHost === false) {
|
||||||
|
return yield* currIter;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pathToManifestFile = join(
|
||||||
|
context.root,
|
||||||
|
project.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateDevRemotes(options, workspaceProjects);
|
||||||
|
|
||||||
|
const moduleFederationConfig = getModuleFederationConfig(
|
||||||
|
project.targets.build.options.tsConfig,
|
||||||
|
context.root,
|
||||||
|
project.root,
|
||||||
|
'angular'
|
||||||
|
);
|
||||||
|
|
||||||
|
const remotes = getRemotes(
|
||||||
|
options.devRemotes,
|
||||||
|
options.skipRemotes,
|
||||||
|
moduleFederationConfig,
|
||||||
|
{
|
||||||
|
projectName: project.name,
|
||||||
|
projectGraph: context.projectGraph,
|
||||||
|
root: context.root,
|
||||||
|
},
|
||||||
|
pathToManifestFile
|
||||||
|
);
|
||||||
|
|
||||||
|
if (remotes.devRemotes.length > 0 && !schema.staticRemotesPort) {
|
||||||
|
options.staticRemotesPort = options.devRemotes.reduce((portToUse, r) => {
|
||||||
|
const remotePort =
|
||||||
|
context.projectGraph.nodes[r].data.targets['serve'].options.port;
|
||||||
|
if (remotePort >= portToUse) {
|
||||||
|
return remotePort + 1;
|
||||||
|
}
|
||||||
|
}, options.staticRemotesPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
await buildStaticRemotes(remotes, nxBin, context, options);
|
||||||
|
|
||||||
|
const devRemoteIters = await startDevRemotes(
|
||||||
|
remotes,
|
||||||
|
workspaceProjects,
|
||||||
|
options,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
|
||||||
|
const staticRemotesIter =
|
||||||
|
remotes.staticRemotes.length > 0
|
||||||
|
? startStaticRemotesFileServer(remotes, context, options)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const removeBaseUrlEmission = (iter: AsyncIterable<unknown>) =>
|
||||||
|
mapAsyncIterable(iter, (v) => ({
|
||||||
|
...v,
|
||||||
|
baseUrl: undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return yield* combineAsyncIterables(
|
||||||
|
removeBaseUrlEmission(currIter),
|
||||||
|
...devRemoteIters.map(removeBaseUrlEmission),
|
||||||
|
...(staticRemotesIter ? [removeBaseUrlEmission(staticRemotesIter)] : []),
|
||||||
|
createAsyncIterable<{ success: true; baseUrl: string }>(
|
||||||
|
async ({ next, done }) => {
|
||||||
|
if (!options.isInitialHost) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (remotes.remotePorts.length === 0) {
|
||||||
|
logger.info(
|
||||||
|
`NX All remotes started, server ready at http://localhost:${options.port}`
|
||||||
|
);
|
||||||
|
next({ success: true, baseUrl: `http://localhost:${options.port}` });
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const portsToWaitFor = staticRemotesIter
|
||||||
|
? [options.staticRemotesPort, ...remotes.remotePorts]
|
||||||
|
: [...remotes.remotePorts];
|
||||||
|
await Promise.all(
|
||||||
|
portsToWaitFor.map((port) =>
|
||||||
|
waitForPortOpen(port, {
|
||||||
|
retries: 480,
|
||||||
|
retryDelay: 2500,
|
||||||
|
host: 'localhost',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`NX All remotes started, server ready at http://localhost:${options.port}`
|
||||||
|
);
|
||||||
|
next({ success: true, baseUrl: `http://localhost:${options.port}` });
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
`Timed out waiting for remote to start. Check above for any errors.`
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 2,
|
|
||||||
"outputCapture": "direct-nodejs",
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema",
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
"title": "Schema for Module Federation Dev Server",
|
"title": "Schema for Module Federation Dev Server",
|
||||||
|
"outputCapture": "direct-nodejs",
|
||||||
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
"description": "The module-federation-dev-server executor is reserved exclusively for use with host Module Federation applications. It allows the user to specify which remote applications should be served with the host.",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"presets": [
|
"presets": [
|
||||||
@ -149,6 +148,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }],
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"required": ["buildTarget"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": ["browserTarget"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
|
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
|
||||||
}
|
}
|
||||||
@ -113,6 +113,35 @@ export async function createBuilderContext(
|
|||||||
architect['_scheduler'].schedule('..getProjectMetadata', target).output
|
architect['_scheduler'].schedule('..getProjectMetadata', target).output
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getBuilderNameForTarget = (target: Target | string) => {
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
return Promise.resolve(
|
||||||
|
context.projectGraph.nodes[context.projectName].data.targets[target]
|
||||||
|
.executor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.resolve(
|
||||||
|
context.projectGraph.nodes[target.project].data.targets[target.target]
|
||||||
|
.executor
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTargetOptions = (target: Target | string) => {
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
return Promise.resolve({
|
||||||
|
...context.projectGraph.nodes[context.projectName].data.targets[target]
|
||||||
|
.options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
...context.projectGraph.nodes[target.project].data.targets[target.target]
|
||||||
|
.options,
|
||||||
|
...context.projectGraph.nodes[target.project].data.targets[target.target]
|
||||||
|
.configurations[target.configuration],
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const builderContext: import('@angular-devkit/architect').BuilderContext = {
|
const builderContext: import('@angular-devkit/architect').BuilderContext = {
|
||||||
workspaceRoot: context.root,
|
workspaceRoot: context.root,
|
||||||
target: {
|
target: {
|
||||||
@ -127,9 +156,7 @@ export async function createBuilderContext(
|
|||||||
id: 1,
|
id: 1,
|
||||||
currentDirectory: process.cwd(),
|
currentDirectory: process.cwd(),
|
||||||
scheduleTarget: architect.scheduleTarget,
|
scheduleTarget: architect.scheduleTarget,
|
||||||
getBuilderNameForTarget: architectHost.getBuilderNameForTarget,
|
|
||||||
scheduleBuilder: architect.scheduleBuilder,
|
scheduleBuilder: architect.scheduleBuilder,
|
||||||
getTargetOptions: architectHost.getOptionsForTarget,
|
|
||||||
addTeardown(teardown: () => Promise<void> | void) {
|
addTeardown(teardown: () => Promise<void> | void) {
|
||||||
// No-op as Nx doesn't require an implementation of this function
|
// No-op as Nx doesn't require an implementation of this function
|
||||||
return;
|
return;
|
||||||
@ -146,8 +173,10 @@ export async function createBuilderContext(
|
|||||||
// No-op as Nx doesn't require an implementation of this function
|
// No-op as Nx doesn't require an implementation of this function
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
getBuilderNameForTarget,
|
||||||
getProjectMetadata,
|
getProjectMetadata,
|
||||||
validateOptions,
|
validateOptions,
|
||||||
|
getTargetOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
return builderContext;
|
return builderContext;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user