Skip to content

Commit a08133f

Browse files
authored
feat(module-federation): add remote configuration override (#19694)
## Current Behavior The configuration of the served MFE always passed to the remotes. If a new configuration is needed to skip one remote (e.g. `serve:skip-remote1`) but `remote2` then a configuration called `skip-remote1` is needed in the `remote2`. ## Expected Behavior Add an ability to override the configuration and the empty configurations in the remotes can be deleted. ## Related Issue(s) Fixes #19693
1 parent 0066543 commit a08133f

File tree

11 files changed

+206
-17
lines changed

11 files changed

+206
-17
lines changed

docs/generated/packages/angular/executors/module-federation-dev-server.json

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,20 @@
105105
},
106106
"devRemotes": {
107107
"type": "array",
108-
"items": { "type": "string" },
108+
"items": {
109+
"oneOf": [
110+
{ "type": "string" },
111+
{
112+
"type": "object",
113+
"properties": {
114+
"remoteName": { "type": "string" },
115+
"configuration": { "type": "string" }
116+
},
117+
"required": ["remoteName"],
118+
"additionalProperties": false
119+
}
120+
]
121+
},
109122
"description": "List of remote applications to run in development mode (i.e. using serve target).",
110123
"x-priority": "important"
111124
},
@@ -147,7 +160,7 @@
147160
{ "required": ["buildTarget"] },
148161
{ "required": ["browserTarget"] }
149162
],
150-
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Basic Usage\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve them statically also. \nSee an example set up of it below:\n\n```json\n{\n \"serve\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\"\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Serve host with remotes that can be live reloaded\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve a set selection with live reloading enabled also. \nSee an example set up of it below:\n\n```json\n{\n \"serve-with-hmr-remotes\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\",\n \"devRemotes\": [\"remote1\", \"remote2\"]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
163+
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Basic Usage\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve them statically also. \nSee an example set up of it below:\n\n```json\n{\n \"serve\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\"\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Serve host with remotes that can be live reloaded\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve a set selection with live reloading enabled also. \nSee an example set up of it below:\n\n```json\n{\n \"serve-with-hmr-remotes\": {\n \"executor\": \"@nx/angular:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\",\n \"devRemotes\": [\n \"remote1\",\n {\n \"remoteName\": \"remote2\",\n \"configuration\": \"development\"\n }\n ]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n"
151164
},
152165
"description": "Serves host [Module Federation](https://module-federation.io/) applications ([webpack](https://webpack.js.org/)-based) allowing to specify which remote applications should be served with the host.",
153166
"aliases": [],

docs/generated/packages/react/executors/module-federation-dev-server.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,20 @@
1111
"properties": {
1212
"devRemotes": {
1313
"type": "array",
14-
"items": { "type": "string" },
14+
"items": {
15+
"oneOf": [
16+
{ "type": "string" },
17+
{
18+
"type": "object",
19+
"properties": {
20+
"remoteName": { "type": "string" },
21+
"configuration": { "type": "string" }
22+
},
23+
"required": ["remoteName"],
24+
"additionalProperties": false
25+
}
26+
]
27+
},
1528
"description": "List of remote applications to run in development mode (i.e. using serve target).",
1629
"x-priority": "important"
1730
},
@@ -114,6 +127,7 @@
114127
"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."
115128
}
116129
},
130+
"examplesFile": "## Examples\n\n{% tabs %}\n\n{% tab label=\"Basic Usage\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve them statically also. \nSee an example set up of it below:\n\n```json\n{\n \"serve\": {\n \"executor\": \"@nx/react:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\"\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Serve host with remotes that can be live reloaded\" %}\nThe Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve a set selection with live reloading enabled also. \nSee an example set up of it below:\n\n```json\n{\n \"serve-with-hmr-remotes\": {\n \"executor\": \"@nx/react:module-federation-dev-server\",\n \"configurations\": {\n \"production\": {\n \"buildTarget\": \"host:build:production\"\n },\n \"development\": {\n \"buildTarget\": \"host:build:development\"\n }\n },\n \"defaultConfiguration\": \"development\",\n \"options\": {\n \"port\": 4200,\n \"publicHost\": \"http://localhost:4200\",\n \"devRemotes\": [\n \"remote1\",\n {\n \"remoteName\": \"remote2\",\n \"configuration\": \"development\"\n }\n ]\n }\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
117131
"presets": []
118132
},
119133
"description": "Serve a host or remote application.",

packages/angular/docs/module-federation-dev-server-examples.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,13 @@ See an example set up of it below:
4949
"options": {
5050
"port": 4200,
5151
"publicHost": "http://localhost:4200",
52-
"devRemotes": ["remote1", "remote2"]
52+
"devRemotes": [
53+
"remote1",
54+
{
55+
"remoteName": "remote2",
56+
"configuration": "development"
57+
}
58+
]
5359
}
5460
}
5561
}

packages/angular/src/builders/utilities/module-federation.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,24 @@ export function getStaticRemotes(
155155
}
156156

157157
export function validateDevRemotes(
158-
options: { devRemotes?: string[] },
158+
options: {
159+
devRemotes?: (
160+
| string
161+
| {
162+
remoteName: string;
163+
configuration: string;
164+
}
165+
)[];
166+
},
159167
workspaceProjects: Record<string, ProjectConfiguration>
160168
): void {
161169
const invalidDevRemotes =
162-
options.devRemotes?.filter((remote) => !workspaceProjects[remote]) ?? [];
170+
options.devRemotes?.filter(
171+
(remote) =>
172+
!(typeof remote === 'string'
173+
? workspaceProjects[remote]
174+
: workspaceProjects[remote.remoteName])
175+
) ?? [];
163176

164177
if (invalidDevRemotes.length) {
165178
throw new Error(

packages/angular/src/executors/module-federation-dev-server/lib/start-dev-remotes.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,21 @@ export async function startRemotes(
2828
'module-federation-dev-server'
2929
);
3030

31+
const configurationOverride = options.devRemotes.find(
32+
(
33+
r
34+
): r is {
35+
remoteName: string;
36+
configuration: string;
37+
} => typeof r !== 'string' && r.remoteName === app
38+
)?.configuration;
39+
3140
remoteIters.push(
3241
await runExecutor(
3342
{
3443
project: app,
3544
target,
36-
configuration: context.configurationName,
45+
configuration: configurationOverride ?? context.configurationName,
3746
},
3847
{
3948
...(target === 'serve' ? { verbose: options.verbose ?? false } : {}),

packages/angular/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,12 @@ export async function* moduleFederationDevServerExecutor(
108108
'angular'
109109
);
110110

111+
const remoteNames = options.devRemotes?.map((r) =>
112+
typeof r === 'string' ? r : r.remoteName
113+
);
114+
111115
const remotes = getRemotes(
112-
options.devRemotes,
116+
remoteNames,
113117
options.skipRemotes,
114118
moduleFederationConfig,
115119
{
@@ -122,8 +126,10 @@ export async function* moduleFederationDevServerExecutor(
122126

123127
if (remotes.devRemotes.length > 0 && !schema.staticRemotesPort) {
124128
options.staticRemotesPort = options.devRemotes.reduce((portToUse, r) => {
129+
const remoteName = typeof r === 'string' ? r : r.remoteName;
125130
const remotePort =
126-
context.projectGraph.nodes[r].data.targets['serve'].options.port;
131+
context.projectGraph.nodes[remoteName].data.targets['serve'].options
132+
.port;
127133
if (remotePort >= portToUse) {
128134
return remotePort + 1;
129135
} else {

packages/angular/src/executors/module-federation-dev-server/schema.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ interface BaseSchema {
1616
hmr?: boolean;
1717
watch?: boolean;
1818
poll?: number;
19-
devRemotes?: string[];
19+
devRemotes?: (
20+
| string
21+
| {
22+
remoteName: string;
23+
configuration: string;
24+
}
25+
)[];
2026
skipRemotes?: string[];
2127
pathToManifestFile?: string;
2228
static?: boolean;

packages/angular/src/executors/module-federation-dev-server/schema.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,24 @@
112112
"devRemotes": {
113113
"type": "array",
114114
"items": {
115-
"type": "string"
115+
"oneOf": [
116+
{
117+
"type": "string"
118+
},
119+
{
120+
"type": "object",
121+
"properties": {
122+
"remoteName": {
123+
"type": "string"
124+
},
125+
"configuration": {
126+
"type": "string"
127+
}
128+
},
129+
"required": ["remoteName"],
130+
"additionalProperties": false
131+
}
132+
]
116133
},
117134
"description": "List of remote applications to run in development mode (i.e. using serve target).",
118135
"x-priority": "important"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
## Examples
2+
3+
{% tabs %}
4+
5+
{% tab label="Basic Usage" %}
6+
The Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve them statically also.
7+
See an example set up of it below:
8+
9+
```json
10+
{
11+
"serve": {
12+
"executor": "@nx/react:module-federation-dev-server",
13+
"configurations": {
14+
"production": {
15+
"buildTarget": "host:build:production"
16+
},
17+
"development": {
18+
"buildTarget": "host:build:development"
19+
}
20+
},
21+
"defaultConfiguration": "development",
22+
"options": {
23+
"port": 4200,
24+
"publicHost": "http://localhost:4200"
25+
}
26+
}
27+
}
28+
```
29+
30+
{% /tab %}
31+
32+
{% tab label="Serve host with remotes that can be live reloaded" %}
33+
The Module Federation Dev Server will serve a host application and find the remote applications associated with the host and serve a set selection with live reloading enabled also.
34+
See an example set up of it below:
35+
36+
```json
37+
{
38+
"serve-with-hmr-remotes": {
39+
"executor": "@nx/react:module-federation-dev-server",
40+
"configurations": {
41+
"production": {
42+
"buildTarget": "host:build:production"
43+
},
44+
"development": {
45+
"buildTarget": "host:build:development"
46+
}
47+
},
48+
"defaultConfiguration": "development",
49+
"options": {
50+
"port": 4200,
51+
"publicHost": "http://localhost:4200",
52+
"devRemotes": [
53+
"remote1",
54+
{
55+
"remoteName": "remote2",
56+
"configuration": "development"
57+
}
58+
]
59+
}
60+
}
61+
}
62+
```
63+
64+
{% /tab %}
65+
66+
{% /tabs %}

packages/react/src/executors/module-federation-dev-server/module-federation-dev-server.impl.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ import { existsSync } from 'fs';
2626
import { extname } from 'path';
2727

2828
type ModuleFederationDevServerOptions = WebDevServerOptions & {
29-
devRemotes?: string[];
29+
devRemotes?: (
30+
| string
31+
| {
32+
remoteName: string;
33+
configuration: string;
34+
}
35+
)[];
3036
skipRemotes?: string[];
3137
static?: boolean;
3238
isInitialHost?: boolean;
@@ -112,6 +118,15 @@ async function startRemotes(
112118
'module-federation-dev-server'
113119
);
114120

121+
const configurationOverride = options.devRemotes?.find(
122+
(
123+
r
124+
): r is {
125+
remoteName: string;
126+
configuration: string;
127+
} => typeof r !== 'string' && r.remoteName === app
128+
)?.configuration;
129+
115130
const overrides =
116131
target === 'serve'
117132
? {
@@ -130,7 +145,7 @@ async function startRemotes(
130145
{
131146
project: app,
132147
target,
133-
configuration: context.configurationName,
148+
configuration: configurationOverride ?? context.configurationName,
134149
},
135150
overrides,
136151
context
@@ -307,8 +322,12 @@ export default async function* moduleFederationDevServer(
307322
'react'
308323
);
309324

325+
const remoteNames = options.devRemotes?.map((r) =>
326+
typeof r === 'string' ? r : r.remoteName
327+
);
328+
310329
const remotes = getRemotes(
311-
options.devRemotes,
330+
remoteNames,
312331
options.skipRemotes,
313332
moduleFederationConfig,
314333
{
@@ -321,8 +340,10 @@ export default async function* moduleFederationDevServer(
321340

322341
if (remotes.devRemotes.length > 0 && !initialStaticRemotesPorts) {
323342
options.staticRemotesPort = options.devRemotes.reduce((portToUse, r) => {
343+
const remoteName = typeof r === 'string' ? r : r.remoteName;
324344
const remotePort =
325-
context.projectGraph.nodes[r].data.targets['serve'].options.port;
345+
context.projectGraph.nodes[remoteName].data.targets['serve'].options
346+
.port;
326347
if (remotePort >= portToUse) {
327348
return remotePort + 1;
328349
} else {

packages/react/src/executors/module-federation-dev-server/schema.json

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,24 @@
99
"devRemotes": {
1010
"type": "array",
1111
"items": {
12-
"type": "string"
12+
"oneOf": [
13+
{
14+
"type": "string"
15+
},
16+
{
17+
"type": "object",
18+
"properties": {
19+
"remoteName": {
20+
"type": "string"
21+
},
22+
"configuration": {
23+
"type": "string"
24+
}
25+
},
26+
"required": ["remoteName"],
27+
"additionalProperties": false
28+
}
29+
]
1330
},
1431
"description": "List of remote applications to run in development mode (i.e. using serve target).",
1532
"x-priority": "important"
@@ -114,5 +131,6 @@
114131
"type": "string",
115132
"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."
116133
}
117-
}
134+
},
135+
"examplesFile": "../../../docs/module-federation-dev-server-examples.md"
118136
}

0 commit comments

Comments
 (0)