feat(angular): add module-federation-dev-ssr builder (#13496)
This commit is contained in:
parent
4e672b2ab9
commit
0ce6735084
@ -3941,6 +3941,95 @@
|
|||||||
"aliases": [],
|
"aliases": [],
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"path": "/packages/angular/src/builders/module-federation-dev-server/schema.json"
|
"path": "/packages/angular/src/builders/module-federation-dev-server/schema.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module-federation-dev-ssr",
|
||||||
|
"implementation": "/packages/angular/src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl.ts",
|
||||||
|
"schema": {
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"title": "Module Federation SSR Dev Server Target",
|
||||||
|
"description": "SSR Dev Server target options for Module Federation host applications.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"browserTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Browser target to build.",
|
||||||
|
"pattern": ".+:.+(:.+)?"
|
||||||
|
},
|
||||||
|
"serverTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Server target to build.",
|
||||||
|
"pattern": ".+:.+(:.+)?"
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host to listen on.",
|
||||||
|
"default": "localhost"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 4200,
|
||||||
|
"description": "Port to start the development server at. Default is 4200. Pass 0 to get a dynamically assigned port."
|
||||||
|
},
|
||||||
|
"publicHost": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The URL that the browser client should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies."
|
||||||
|
},
|
||||||
|
"open": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Opens the url in default browser.",
|
||||||
|
"default": false,
|
||||||
|
"alias": "o"
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Log progress to the console while building."
|
||||||
|
},
|
||||||
|
"inspect": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Launch the development server in inspector mode and listen on address and port '127.0.0.1:9229'.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"ssl": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Serve using HTTPS.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sslKey": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "SSL key to use for serving HTTPS."
|
||||||
|
},
|
||||||
|
"sslCert": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "SSL certificate to use for serving HTTPS."
|
||||||
|
},
|
||||||
|
"proxyConfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Proxy configuration file."
|
||||||
|
},
|
||||||
|
"devRemotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "List of remote applications to run in development mode (i.e. using serve target)."
|
||||||
|
},
|
||||||
|
"skipRemotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||||
|
},
|
||||||
|
"verbose": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Adds more details to output logging."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["browserTarget", "serverTarget"],
|
||||||
|
"presets": []
|
||||||
|
},
|
||||||
|
"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.",
|
||||||
|
"aliases": [],
|
||||||
|
"hidden": false,
|
||||||
|
"path": "/packages/angular/src/builders/module-federation-dev-ssr/schema.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,8 @@
|
|||||||
"webpack-browser",
|
"webpack-browser",
|
||||||
"webpack-dev-server",
|
"webpack-dev-server",
|
||||||
"webpack-server",
|
"webpack-server",
|
||||||
"module-federation-dev-server"
|
"module-federation-dev-server",
|
||||||
|
"module-federation-dev-ssr"
|
||||||
],
|
],
|
||||||
"generators": [
|
"generators": [
|
||||||
"add-linting",
|
"add-linting",
|
||||||
|
|||||||
23
package.json
23
package.json
@ -25,19 +25,19 @@
|
|||||||
"lint": "nx workspace-lint"
|
"lint": "nx workspace-lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/architect": "~0.1500.0",
|
"@angular-devkit/architect": "~0.1500.1",
|
||||||
"@angular-devkit/build-angular": "~15.0.0",
|
"@angular-devkit/build-angular": "~15.0.1",
|
||||||
"@angular-devkit/core": "~15.0.0",
|
"@angular-devkit/core": "~15.0.1",
|
||||||
"@angular-devkit/schematics": "~15.0.0",
|
"@angular-devkit/schematics": "~15.0.1",
|
||||||
"@angular-eslint/eslint-plugin": "~15.0.0",
|
"@angular-eslint/eslint-plugin": "~15.0.0",
|
||||||
"@angular-eslint/eslint-plugin-template": "~15.0.0",
|
"@angular-eslint/eslint-plugin-template": "~15.0.0",
|
||||||
"@angular-eslint/template-parser": "~15.0.0",
|
"@angular-eslint/template-parser": "~15.0.0",
|
||||||
"@angular/cli": "~15.0.0",
|
"@angular/cli": "~15.0.1",
|
||||||
"@angular/common": "~15.0.0",
|
"@angular/common": "~15.0.1",
|
||||||
"@angular/compiler": "~15.0.0",
|
"@angular/compiler": "~15.0.1",
|
||||||
"@angular/compiler-cli": "~15.0.0",
|
"@angular/compiler-cli": "~15.0.1",
|
||||||
"@angular/core": "~15.0.0",
|
"@angular/core": "~15.0.1",
|
||||||
"@angular/router": "~15.0.0",
|
"@angular/router": "~15.0.1",
|
||||||
"@babel/core": "^7.15.0",
|
"@babel/core": "^7.15.0",
|
||||||
"@babel/helper-create-regexp-features-plugin": "^7.14.5",
|
"@babel/helper-create-regexp-features-plugin": "^7.14.5",
|
||||||
"@babel/preset-typescript": "^7.15.0",
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
@ -52,6 +52,7 @@
|
|||||||
"@ngrx/effects": "~14.0.0",
|
"@ngrx/effects": "~14.0.0",
|
||||||
"@ngrx/router-store": "~14.0.0",
|
"@ngrx/router-store": "~14.0.0",
|
||||||
"@ngrx/store": "~14.0.0",
|
"@ngrx/store": "~14.0.0",
|
||||||
|
"@nguniversal/builders": "~15.0.0",
|
||||||
"@nrwl/cypress": "15.3.0-beta.6",
|
"@nrwl/cypress": "15.3.0-beta.6",
|
||||||
"@nrwl/devkit": "15.3.0-beta.6",
|
"@nrwl/devkit": "15.3.0-beta.6",
|
||||||
"@nrwl/eslint-plugin-nx": "15.3.0-beta.6",
|
"@nrwl/eslint-plugin-nx": "15.3.0-beta.6",
|
||||||
@ -73,7 +74,7 @@
|
|||||||
"@rollup/plugin-image": "^2.1.0",
|
"@rollup/plugin-image": "^2.1.0",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.0.4",
|
"@rollup/plugin-node-resolve": "^13.0.4",
|
||||||
"@schematics/angular": "~15.0.0",
|
"@schematics/angular": "~15.0.1",
|
||||||
"@storybook/addon-essentials": "~6.5.9",
|
"@storybook/addon-essentials": "~6.5.9",
|
||||||
"@storybook/angular": "^6.5.12",
|
"@storybook/angular": "^6.5.12",
|
||||||
"@storybook/builder-webpack5": "~6.5.9",
|
"@storybook/builder-webpack5": "~6.5.9",
|
||||||
|
|||||||
@ -57,6 +57,11 @@
|
|||||||
"schema": "./src/builders/module-federation-dev-server/schema.json",
|
"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."
|
"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": {
|
||||||
|
"implementation": "./src/builders/module-federation-dev-ssr/module-federation-dev-ssr.impl",
|
||||||
|
"schema": "./src/builders/module-federation-dev-ssr/schema.json",
|
||||||
|
"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-server": {
|
"file-server": {
|
||||||
"implementation": "./src/executors/file-server/compat",
|
"implementation": "./src/executors/file-server/compat",
|
||||||
"schema": "./src/executors/file-server/schema.json",
|
"schema": "./src/executors/file-server/schema.json",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export * from './src/builders/module-federation-dev-server/module-federation-dev-server.impl';
|
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/webpack-browser/webpack-browser.impl';
|
export * from './src/builders/webpack-browser/webpack-browser.impl';
|
||||||
export * from './src/builders/webpack-dev-server/webpack-dev-server.impl';
|
export * from './src/builders/webpack-dev-server/webpack-dev-server.impl';
|
||||||
export * from './src/builders/webpack-server/webpack-server.impl';
|
export * from './src/builders/webpack-server/webpack-server.impl';
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
"@nrwl/",
|
"@nrwl/",
|
||||||
"@angular-devkit",
|
"@angular-devkit",
|
||||||
"@angular-eslint/",
|
"@angular-eslint/",
|
||||||
|
"@nguniversal/builders",
|
||||||
"@schematics",
|
"@schematics",
|
||||||
"@phenomnomnominal/tsquery",
|
"@phenomnomnominal/tsquery",
|
||||||
"@typescript-eslint/",
|
"@typescript-eslint/",
|
||||||
|
|||||||
@ -39,7 +39,8 @@
|
|||||||
"migrations": "./migrations.json"
|
"migrations": "./migrations.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/schematics": "~15.0.0",
|
"@angular-devkit/schematics": "~15.0.1",
|
||||||
|
"@nguniversal/builders": "~15.0.0",
|
||||||
"@nrwl/cypress": "file:../cypress",
|
"@nrwl/cypress": "file:../cypress",
|
||||||
"@nrwl/devkit": "file:../devkit",
|
"@nrwl/devkit": "file:../devkit",
|
||||||
"@nrwl/jest": "file:../jest",
|
"@nrwl/jest": "file:../jest",
|
||||||
@ -48,7 +49,7 @@
|
|||||||
"@nrwl/webpack": "file:../webpack",
|
"@nrwl/webpack": "file:../webpack",
|
||||||
"@nrwl/workspace": "file:../workspace",
|
"@nrwl/workspace": "file:../workspace",
|
||||||
"@phenomnomnominal/tsquery": "4.1.1",
|
"@phenomnomnominal/tsquery": "4.1.1",
|
||||||
"@schematics/angular": "~15.0.0",
|
"@schematics/angular": "~15.0.1",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "^3.5.1",
|
||||||
"http-server": "^14.1.0",
|
"http-server": "^14.1.0",
|
||||||
|
|||||||
@ -1,140 +1,19 @@
|
|||||||
import type { Schema } from './schema';
|
import type { Schema } from './schema';
|
||||||
import {
|
import {
|
||||||
ProjectConfiguration,
|
|
||||||
readCachedProjectGraph,
|
readCachedProjectGraph,
|
||||||
Remotes,
|
|
||||||
workspaceRoot,
|
workspaceRoot,
|
||||||
Workspaces,
|
Workspaces,
|
||||||
} from '@nrwl/devkit';
|
} from '@nrwl/devkit';
|
||||||
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
|
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
|
||||||
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
|
import { BuilderContext, createBuilder } from '@angular-devkit/architect';
|
||||||
import { JsonObject } from '@angular-devkit/core';
|
import { JsonObject } from '@angular-devkit/core';
|
||||||
import { join } from 'path';
|
|
||||||
import { executeWebpackDevServerBuilder } from '../webpack-dev-server/webpack-dev-server.impl';
|
import { executeWebpackDevServerBuilder } from '../webpack-dev-server/webpack-dev-server.impl';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
|
||||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||||
|
import {
|
||||||
function getDynamicRemotes(
|
getDynamicRemotes,
|
||||||
project: ProjectConfiguration,
|
getStaticRemotes,
|
||||||
context: BuilderContext,
|
validateDevRemotes,
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>,
|
} from '../utilities/module-federation';
|
||||||
remotesToSkip: Set<string>
|
|
||||||
): string[] {
|
|
||||||
// check for dynamic remotes
|
|
||||||
// we should only check for dynamic based on what we generate
|
|
||||||
// and fallback to empty array
|
|
||||||
|
|
||||||
const standardPathToGeneratedMFManifestJson = join(
|
|
||||||
context.workspaceRoot,
|
|
||||||
project.sourceRoot,
|
|
||||||
'assets/module-federation.manifest.json'
|
|
||||||
);
|
|
||||||
if (!existsSync(standardPathToGeneratedMFManifestJson)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleFederationManifestJson = readFileSync(
|
|
||||||
standardPathToGeneratedMFManifestJson,
|
|
||||||
'utf-8'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!moduleFederationManifestJson) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// This should have shape of
|
|
||||||
// {
|
|
||||||
// "remoteName": "remoteLocation",
|
|
||||||
// }
|
|
||||||
const parsedManifest = JSON.parse(moduleFederationManifestJson);
|
|
||||||
if (
|
|
||||||
!Object.keys(parsedManifest).every(
|
|
||||||
(key) =>
|
|
||||||
typeof key === 'string' && typeof parsedManifest[key] === 'string'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const dynamicRemotes = Object.entries(parsedManifest)
|
|
||||||
.map(([remoteName]) => remoteName)
|
|
||||||
.filter((r) => !remotesToSkip.has(r));
|
|
||||||
const invalidDynamicRemotes = dynamicRemotes.filter(
|
|
||||||
(remote) => !workspaceProjects[remote]
|
|
||||||
);
|
|
||||||
if (invalidDynamicRemotes.length) {
|
|
||||||
throw new Error(
|
|
||||||
invalidDynamicRemotes.length === 1
|
|
||||||
? `Invalid dynamic remote configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes[0]}.`
|
|
||||||
: `Invalid dynamic remotes configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes.join(
|
|
||||||
', '
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dynamicRemotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStaticRemotes(
|
|
||||||
project: ProjectConfiguration,
|
|
||||||
context: BuilderContext,
|
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>,
|
|
||||||
remotesToSkip: Set<string>
|
|
||||||
): string[] {
|
|
||||||
const mfConfigPath = join(
|
|
||||||
context.workspaceRoot,
|
|
||||||
project.root,
|
|
||||||
'module-federation.config.js'
|
|
||||||
);
|
|
||||||
|
|
||||||
let mfeConfig: { remotes: Remotes };
|
|
||||||
try {
|
|
||||||
mfeConfig = require(mfConfigPath);
|
|
||||||
} catch {
|
|
||||||
throw new Error(
|
|
||||||
`Could not load ${mfConfigPath}. Was this project generated with "@nrwl/angular:host"?`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const remotesConfig = mfeConfig.remotes.length > 0 ? mfeConfig.remotes : [];
|
|
||||||
const staticRemotes = remotesConfig
|
|
||||||
.map((remoteDefinition) =>
|
|
||||||
Array.isArray(remoteDefinition) ? remoteDefinition[0] : remoteDefinition
|
|
||||||
)
|
|
||||||
.filter((r) => !remotesToSkip.has(r));
|
|
||||||
|
|
||||||
const invalidStaticRemotes = staticRemotes.filter(
|
|
||||||
(remote) => !workspaceProjects[remote]
|
|
||||||
);
|
|
||||||
if (invalidStaticRemotes.length) {
|
|
||||||
throw new Error(
|
|
||||||
invalidStaticRemotes.length === 1
|
|
||||||
? `Invalid static remote configured in "${mfConfigPath}": ${invalidStaticRemotes[0]}.`
|
|
||||||
: `Invalid static remotes configured in "${mfConfigPath}": ${invalidStaticRemotes.join(
|
|
||||||
', '
|
|
||||||
)}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return staticRemotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateDevRemotes(
|
|
||||||
options: Schema,
|
|
||||||
workspaceProjects: Record<string, ProjectConfiguration>
|
|
||||||
): void {
|
|
||||||
const invalidDevRemotes = options.devRemotes?.filter(
|
|
||||||
(remote) => !workspaceProjects[remote]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (invalidDevRemotes.length) {
|
|
||||||
throw new Error(
|
|
||||||
invalidDevRemotes.length === 1
|
|
||||||
? `Invalid dev remote provided: ${invalidDevRemotes[0]}.`
|
|
||||||
: `Invalid dev remotes provided: ${invalidDevRemotes.join(', ')}.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function executeModuleFederationDevServerBuilder(
|
export function executeModuleFederationDevServerBuilder(
|
||||||
schema: Schema,
|
schema: Schema,
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
import type { Schema } from './schema';
|
||||||
|
import type { BuilderContext } from '@angular-devkit/architect';
|
||||||
|
import { createBuilder } from '@angular-devkit/architect';
|
||||||
|
import type { JsonObject } from '@angular-devkit/core';
|
||||||
|
import { executeSSRDevServerBuilder } from '@nguniversal/builders';
|
||||||
|
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||||
|
import {
|
||||||
|
readCachedProjectGraph,
|
||||||
|
workspaceRoot,
|
||||||
|
Workspaces,
|
||||||
|
} from '@nrwl/devkit';
|
||||||
|
import {
|
||||||
|
getDynamicRemotes,
|
||||||
|
getStaticRemotes,
|
||||||
|
validateDevRemotes,
|
||||||
|
} from '../utilities/module-federation';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
import { from } from 'rxjs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { execSync, fork } from 'child_process';
|
||||||
|
|
||||||
|
export function executeModuleFederationDevSSRBuilder(
|
||||||
|
schema: Schema,
|
||||||
|
context: BuilderContext
|
||||||
|
) {
|
||||||
|
const { ...options } = schema;
|
||||||
|
const projectGraph = readCachedProjectGraph();
|
||||||
|
const { projects: workspaceProjects } =
|
||||||
|
readProjectsConfigurationFromProjectGraph(projectGraph);
|
||||||
|
const ws = new Workspaces(workspaceRoot);
|
||||||
|
const project = workspaceProjects[context.target.project];
|
||||||
|
|
||||||
|
validateDevRemotes(options, workspaceProjects);
|
||||||
|
|
||||||
|
const remotesToSkip = new Set(options.skipRemotes ?? []);
|
||||||
|
const staticRemotes = getStaticRemotes(
|
||||||
|
project,
|
||||||
|
context,
|
||||||
|
workspaceProjects,
|
||||||
|
remotesToSkip
|
||||||
|
);
|
||||||
|
const dynamicRemotes = getDynamicRemotes(
|
||||||
|
project,
|
||||||
|
context,
|
||||||
|
workspaceProjects,
|
||||||
|
remotesToSkip
|
||||||
|
);
|
||||||
|
const remotes = [...staticRemotes, ...dynamicRemotes];
|
||||||
|
|
||||||
|
const devServeRemotes = !options.devRemotes
|
||||||
|
? []
|
||||||
|
: Array.isArray(options.devRemotes)
|
||||||
|
? options.devRemotes
|
||||||
|
: [options.devRemotes];
|
||||||
|
|
||||||
|
const remoteProcessPromises = [];
|
||||||
|
for (const remote of remotes) {
|
||||||
|
const isDev = devServeRemotes.includes(remote);
|
||||||
|
const target = isDev ? 'serve-ssr' : 'static-server';
|
||||||
|
|
||||||
|
if (!workspaceProjects[remote].targets?.[target]) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find "${target}" target in "${remote}" project.`
|
||||||
|
);
|
||||||
|
} else if (!workspaceProjects[remote].targets?.[target].executor) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not find executor for "${target}" target in "${remote}" project.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const runOptions: { verbose?: boolean } = {};
|
||||||
|
if (options.verbose) {
|
||||||
|
const [collection, executor] =
|
||||||
|
workspaceProjects[remote].targets[target].executor.split(':');
|
||||||
|
const { schema } = ws.readExecutor(collection, executor);
|
||||||
|
|
||||||
|
if (schema.additionalProperties || 'verbose' in schema.properties) {
|
||||||
|
runOptions.verbose = options.verbose;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const remotePromise = new Promise<void>((res, rej) => {
|
||||||
|
const remoteProject = workspaceProjects[remote];
|
||||||
|
const remoteServerOutput = join(
|
||||||
|
workspaceRoot,
|
||||||
|
remoteProject.targets.server.options.outputPath,
|
||||||
|
'main.js'
|
||||||
|
);
|
||||||
|
execSync(
|
||||||
|
`npx nx run ${remote}:server${
|
||||||
|
context.target.configuration ? `:${context.target.configuration}` : ''
|
||||||
|
}`,
|
||||||
|
{ stdio: 'inherit' }
|
||||||
|
);
|
||||||
|
const child = fork(remoteServerOutput, {
|
||||||
|
env: remoteProject.targets['serve-ssr'].options.port,
|
||||||
|
});
|
||||||
|
child.on('message', (msg) => {
|
||||||
|
if (msg === 'nx.server.ready') {
|
||||||
|
res();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
remoteProcessPromises.push(remotePromise);
|
||||||
|
}
|
||||||
|
|
||||||
|
return from(Promise.all(remoteProcessPromises)).pipe(
|
||||||
|
switchMap(() => executeSSRDevServerBuilder(options, context))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createBuilder<JsonObject & Schema>(
|
||||||
|
executeModuleFederationDevSSRBuilder
|
||||||
|
);
|
||||||
16
packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts
vendored
Normal file
16
packages/angular/src/builders/module-federation-dev-ssr/schema.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface Schema {
|
||||||
|
browserTarget: string;
|
||||||
|
serverTarget: string;
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
progress: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
publicHost?: string;
|
||||||
|
ssl?: boolean;
|
||||||
|
sslKey?: string;
|
||||||
|
sslCert?: string;
|
||||||
|
proxyConfig?: string;
|
||||||
|
devRemotes?: string[];
|
||||||
|
skipRemotes?: string[];
|
||||||
|
verbose?: boolean;
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"title": "Module Federation SSR Dev Server Target",
|
||||||
|
"description": "SSR Dev Server target options for Module Federation host applications.",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"browserTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Browser target to build.",
|
||||||
|
"pattern": ".+:.+(:.+)?"
|
||||||
|
},
|
||||||
|
"serverTarget": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Server target to build.",
|
||||||
|
"pattern": ".+:.+(:.+)?"
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Host to listen on.",
|
||||||
|
"default": "localhost"
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 4200,
|
||||||
|
"description": "Port to start the development server at. Default is 4200. Pass 0 to get a dynamically assigned port."
|
||||||
|
},
|
||||||
|
"publicHost": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The URL that the browser client should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies."
|
||||||
|
},
|
||||||
|
"open": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Opens the url in default browser.",
|
||||||
|
"default": false,
|
||||||
|
"alias": "o"
|
||||||
|
},
|
||||||
|
"progress": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Log progress to the console while building."
|
||||||
|
},
|
||||||
|
"inspect": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Launch the development server in inspector mode and listen on address and port '127.0.0.1:9229'.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"ssl": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Serve using HTTPS.",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"sslKey": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "SSL key to use for serving HTTPS."
|
||||||
|
},
|
||||||
|
"sslCert": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "SSL certificate to use for serving HTTPS."
|
||||||
|
},
|
||||||
|
"proxyConfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Proxy configuration file."
|
||||||
|
},
|
||||||
|
"devRemotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of remote applications to run in development mode (i.e. using serve target)."
|
||||||
|
},
|
||||||
|
"skipRemotes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||||
|
},
|
||||||
|
"verbose": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Adds more details to output logging."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["browserTarget", "serverTarget"]
|
||||||
|
}
|
||||||
127
packages/angular/src/builders/utilities/module-federation.ts
Normal file
127
packages/angular/src/builders/utilities/module-federation.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
|
||||||
|
import { BuilderContext } from '@angular-devkit/architect';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
import { Remotes } from '@nrwl/devkit';
|
||||||
|
|
||||||
|
export function getDynamicRemotes(
|
||||||
|
project: ProjectConfiguration,
|
||||||
|
context: BuilderContext,
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
remotesToSkip: Set<string>
|
||||||
|
): string[] {
|
||||||
|
// check for dynamic remotes
|
||||||
|
// we should only check for dynamic based on what we generate
|
||||||
|
// and fallback to empty array
|
||||||
|
|
||||||
|
const standardPathToGeneratedMFManifestJson = join(
|
||||||
|
context.workspaceRoot,
|
||||||
|
project.sourceRoot,
|
||||||
|
'assets/module-federation.manifest.json'
|
||||||
|
);
|
||||||
|
if (!existsSync(standardPathToGeneratedMFManifestJson)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleFederationManifestJson = readFileSync(
|
||||||
|
standardPathToGeneratedMFManifestJson,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!moduleFederationManifestJson) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should have shape of
|
||||||
|
// {
|
||||||
|
// "remoteName": "remoteLocation",
|
||||||
|
// }
|
||||||
|
const parsedManifest = JSON.parse(moduleFederationManifestJson);
|
||||||
|
if (
|
||||||
|
!Object.keys(parsedManifest).every(
|
||||||
|
(key) =>
|
||||||
|
typeof key === 'string' && typeof parsedManifest[key] === 'string'
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicRemotes = Object.entries(parsedManifest)
|
||||||
|
.map(([remoteName]) => remoteName)
|
||||||
|
.filter((r) => !remotesToSkip.has(r));
|
||||||
|
const invalidDynamicRemotes = dynamicRemotes.filter(
|
||||||
|
(remote) => !workspaceProjects[remote]
|
||||||
|
);
|
||||||
|
if (invalidDynamicRemotes.length) {
|
||||||
|
throw new Error(
|
||||||
|
invalidDynamicRemotes.length === 1
|
||||||
|
? `Invalid dynamic remote configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes[0]}.`
|
||||||
|
: `Invalid dynamic remotes configured in "${standardPathToGeneratedMFManifestJson}": ${invalidDynamicRemotes.join(
|
||||||
|
', '
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicRemotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStaticRemotes(
|
||||||
|
project: ProjectConfiguration,
|
||||||
|
context: BuilderContext,
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>,
|
||||||
|
remotesToSkip: Set<string>
|
||||||
|
): string[] {
|
||||||
|
const mfConfigPath = join(
|
||||||
|
context.workspaceRoot,
|
||||||
|
project.root,
|
||||||
|
'module-federation.config.js'
|
||||||
|
);
|
||||||
|
|
||||||
|
let mfeConfig: { remotes: Remotes };
|
||||||
|
try {
|
||||||
|
mfeConfig = require(mfConfigPath);
|
||||||
|
} catch {
|
||||||
|
throw new Error(
|
||||||
|
`Could not load ${mfConfigPath}. Was this project generated with "@nrwl/angular:host"?`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remotesConfig = mfeConfig.remotes.length > 0 ? mfeConfig.remotes : [];
|
||||||
|
const staticRemotes = remotesConfig
|
||||||
|
.map((remoteDefinition) =>
|
||||||
|
Array.isArray(remoteDefinition) ? remoteDefinition[0] : remoteDefinition
|
||||||
|
)
|
||||||
|
.filter((r) => !remotesToSkip.has(r));
|
||||||
|
|
||||||
|
const invalidStaticRemotes = staticRemotes.filter(
|
||||||
|
(remote) => !workspaceProjects[remote]
|
||||||
|
);
|
||||||
|
if (invalidStaticRemotes.length) {
|
||||||
|
throw new Error(
|
||||||
|
invalidStaticRemotes.length === 1
|
||||||
|
? `Invalid static remote configured in "${mfConfigPath}": ${invalidStaticRemotes[0]}.`
|
||||||
|
: `Invalid static remotes configured in "${mfConfigPath}": ${invalidStaticRemotes.join(
|
||||||
|
', '
|
||||||
|
)}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return staticRemotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateDevRemotes(
|
||||||
|
options: { devRemotes?: string[] },
|
||||||
|
workspaceProjects: Record<string, ProjectConfiguration>
|
||||||
|
): void {
|
||||||
|
const invalidDevRemotes = options.devRemotes?.filter(
|
||||||
|
(remote) => !workspaceProjects[remote]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (invalidDevRemotes.length) {
|
||||||
|
throw new Error(
|
||||||
|
invalidDevRemotes.length === 1
|
||||||
|
? `Invalid dev remote provided: ${invalidDevRemotes[0]}.`
|
||||||
|
: `Invalid dev remotes provided: ${invalidDevRemotes.join(', ')}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user