@nx/react 17.1.2 → 17.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/mf/dynamic-federation.d.ts +4 -0
  2. package/mf/dynamic-federation.js +75 -0
  3. package/mf/index.d.ts +1 -0
  4. package/mf/index.js +7 -0
  5. package/package.json +6 -6
  6. package/src/executors/module-federation-dev-server/module-federation-dev-server.impl.d.ts +1 -0
  7. package/src/executors/module-federation-dev-server/module-federation-dev-server.impl.js +105 -74
  8. package/src/executors/module-federation-dev-server/schema.json +4 -0
  9. package/src/generators/application/lib/update-jest-config.js +6 -7
  10. package/src/generators/host/files/common/src/app/__fileName__.tsx__tmpl__ +8 -0
  11. package/src/generators/host/files/common/tsconfig.lint.json__tmpl__ +19 -0
  12. package/src/generators/host/files/module-federation/module-federation.config.js__tmpl__ +17 -2
  13. package/src/generators/host/files/module-federation/src/main.ts__tmpl__ +10 -1
  14. package/src/generators/host/files/module-federation-ssr/module-federation.server.config.js__tmpl__ +5 -2
  15. package/src/generators/host/files/module-federation-ssr-ts/module-federation.server.config.ts__tmpl__ +5 -2
  16. package/src/generators/host/files/module-federation-ts/module-federation.config.ts__tmpl__ +17 -2
  17. package/src/generators/host/files/module-federation-ts/src/main.ts__tmpl__ +10 -1
  18. package/src/generators/host/host.js +6 -0
  19. package/src/generators/host/lib/add-module-federation-files.d.ts +2 -1
  20. package/src/generators/host/lib/add-module-federation-files.js +23 -11
  21. package/src/generators/host/lib/setup-ssr-for-host.js +1 -0
  22. package/src/generators/host/schema.d.ts +1 -0
  23. package/src/generators/host/schema.json +5 -0
  24. package/src/generators/init/init.js +2 -2
  25. package/src/generators/remote/files/module-federation/module-federation.config.js__tmpl__ +3 -0
  26. package/src/generators/remote/files/module-federation-ssr-ts/tsconfig.lint.json__tmpl__ +19 -0
  27. package/src/generators/remote/files/module-federation-ts/module-federation.config.ts__tmpl__ +3 -0
  28. package/src/generators/remote/files/module-federation-ts/tsconfig.lint.json__tmpl__ +19 -0
  29. package/src/generators/remote/lib/add-remote-to-dynamic-host.d.ts +2 -0
  30. package/src/generators/remote/lib/add-remote-to-dynamic-host.js +11 -0
  31. package/src/generators/remote/remote.js +10 -0
  32. package/src/generators/remote/schema.d.ts +1 -0
  33. package/src/generators/remote/schema.json +6 -0
  34. package/src/module-federation/utils.js +7 -0
  35. package/src/rules/update-module-federation-project.d.ts +1 -0
  36. package/src/rules/update-module-federation-project.js +8 -0
@@ -0,0 +1,4 @@
1
+ export type ResolveRemoteUrlFunction = (remoteName: string) => string | Promise<string>;
2
+ export declare function setRemoteUrlResolver(_resolveRemoteUrl: ResolveRemoteUrlFunction): void;
3
+ export declare function setRemoteDefinitions(definitions: Record<string, string>): void;
4
+ export declare function loadRemoteModule(remoteName: string, moduleName: string): Promise<any>;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadRemoteModule = exports.setRemoteDefinitions = exports.setRemoteUrlResolver = void 0;
4
+ let remoteUrlDefinitions;
5
+ let resolveRemoteUrl;
6
+ const remoteModuleMap = new Map();
7
+ const remoteContainerMap = new Map();
8
+ let initialSharingScopeCreated = false;
9
+ function setRemoteUrlResolver(_resolveRemoteUrl) {
10
+ resolveRemoteUrl = _resolveRemoteUrl;
11
+ }
12
+ exports.setRemoteUrlResolver = setRemoteUrlResolver;
13
+ function setRemoteDefinitions(definitions) {
14
+ remoteUrlDefinitions = definitions;
15
+ }
16
+ exports.setRemoteDefinitions = setRemoteDefinitions;
17
+ async function loadRemoteModule(remoteName, moduleName) {
18
+ const remoteModuleKey = `${remoteName}:${moduleName}`;
19
+ if (remoteModuleMap.has(remoteModuleKey)) {
20
+ return remoteModuleMap.get(remoteModuleKey);
21
+ }
22
+ const container = remoteContainerMap.has(remoteName)
23
+ ? remoteContainerMap.get(remoteName)
24
+ : await loadRemoteContainer(remoteName);
25
+ const factory = await container.get(moduleName);
26
+ const Module = factory();
27
+ remoteModuleMap.set(remoteModuleKey, Module);
28
+ return Module;
29
+ }
30
+ exports.loadRemoteModule = loadRemoteModule;
31
+ const fetchRemoteModule = (url, remoteName) => {
32
+ return new Promise((resolve, reject) => {
33
+ const script = document.createElement('script');
34
+ script.src = url;
35
+ script.type = 'text/javascript';
36
+ script.async = true;
37
+ script.onload = () => {
38
+ const proxy = {
39
+ get: (request) => window[remoteName].get(request),
40
+ init: (arg) => {
41
+ try {
42
+ window[remoteName].init(arg);
43
+ }
44
+ catch (e) {
45
+ console.error(`Failed to initialize remote ${remoteName}`, e);
46
+ reject(e);
47
+ }
48
+ },
49
+ };
50
+ resolve(proxy);
51
+ };
52
+ script.onerror = () => reject(new Error(`Remote ${remoteName} not found`));
53
+ document.head.appendChild(script);
54
+ });
55
+ };
56
+ async function loadRemoteContainer(remoteName) {
57
+ if (!resolveRemoteUrl && !remoteUrlDefinitions) {
58
+ throw new Error('Call setRemoteDefinitions or setRemoteUrlResolver to allow Dynamic Federation to find the remote apps correctly.');
59
+ }
60
+ if (!initialSharingScopeCreated) {
61
+ initialSharingScopeCreated = true;
62
+ await __webpack_init_sharing__('default');
63
+ }
64
+ const remoteUrl = remoteUrlDefinitions
65
+ ? remoteUrlDefinitions[remoteName]
66
+ : await resolveRemoteUrl(remoteName);
67
+ let containerUrl = remoteUrl;
68
+ if (!remoteUrl.endsWith('.mjs') && !remoteUrl.endsWith('.js')) {
69
+ containerUrl = `${remoteUrl}${remoteUrl.endsWith('/') ? '' : '/'}remoteEntry.js`;
70
+ }
71
+ const container = await fetchRemoteModule(containerUrl, remoteName);
72
+ await container.init(__webpack_share_scopes__.default);
73
+ remoteContainerMap.set(remoteName, container);
74
+ return container;
75
+ }
package/mf/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { loadRemoteModule, setRemoteDefinitions, setRemoteUrlResolver, } from './dynamic-federation';
package/mf/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setRemoteUrlResolver = exports.setRemoteDefinitions = exports.loadRemoteModule = void 0;
4
+ var dynamic_federation_1 = require("./dynamic-federation");
5
+ Object.defineProperty(exports, "loadRemoteModule", { enumerable: true, get: function () { return dynamic_federation_1.loadRemoteModule; } });
6
+ Object.defineProperty(exports, "setRemoteDefinitions", { enumerable: true, get: function () { return dynamic_federation_1.setRemoteDefinitions; } });
7
+ Object.defineProperty(exports, "setRemoteUrlResolver", { enumerable: true, get: function () { return dynamic_federation_1.setRemoteUrlResolver; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/react",
3
- "version": "17.1.2",
3
+ "version": "17.2.0-beta.1",
4
4
  "private": false,
5
5
  "description": "The React plugin for Nx contains executors and generators for managing React applications and libraries within an Nx workspace. It provides:\n\n\n- Integration with libraries such as Jest, Cypress, and Storybook.\n\n- Generators for applications, libraries, components, hooks, and more.\n\n- Library build support for publishing packages to npm or other registries.\n\n- Utilities for automatic workspace refactoring.",
6
6
  "repository": {
@@ -37,11 +37,11 @@
37
37
  "file-loader": "^6.2.0",
38
38
  "minimatch": "3.0.5",
39
39
  "tslib": "^2.3.0",
40
- "@nx/devkit": "17.1.2",
41
- "@nx/js": "17.1.2",
42
- "@nx/eslint": "17.1.2",
43
- "@nx/web": "17.1.2",
44
- "@nrwl/react": "17.1.2"
40
+ "@nx/devkit": "17.2.0-beta.1",
41
+ "@nx/js": "17.2.0-beta.1",
42
+ "@nx/eslint": "17.2.0-beta.1",
43
+ "@nx/web": "17.2.0-beta.1",
44
+ "@nrwl/react": "17.2.0-beta.1"
45
45
  },
46
46
  "publishConfig": {
47
47
  "access": "public"
@@ -6,6 +6,7 @@ type ModuleFederationDevServerOptions = WebDevServerOptions & {
6
6
  static?: boolean;
7
7
  isInitialHost?: boolean;
8
8
  parallel?: number;
9
+ staticRemotesPort?: number;
9
10
  };
10
11
  export default function moduleFederationDevServer(options: ModuleFederationDevServerOptions, context: ExecutorContext): AsyncIterableIterator<{
11
12
  success: boolean;
@@ -7,6 +7,8 @@ const module_federation_1 = require("@nx/webpack/src/utils/module-federation");
7
7
  const async_iterable_1 = require("@nx/devkit/src/utils/async-iterable");
8
8
  const wait_for_port_open_1 = require("@nx/web/src/utils/wait-for-port-open");
9
9
  const child_process_1 = require("child_process");
10
+ const path_1 = require("path");
11
+ const fs_1 = require("fs");
10
12
  function getBuildOptions(buildTarget, context) {
11
13
  const target = (0, devkit_1.parseTargetString)(buildTarget, context);
12
14
  const buildOptions = (0, devkit_1.readTargetOptions)(target, context);
@@ -14,62 +16,46 @@ function getBuildOptions(buildTarget, context) {
14
16
  ...buildOptions,
15
17
  };
16
18
  }
17
- async function* moduleFederationDevServer(options, context) {
18
- // Force Node to resolve to look for the nx binary that is inside node_modules
19
- const nxBin = require.resolve('nx/bin/nx');
20
- const currIter = options.static
21
- ? (0, file_server_impl_1.default)({
22
- ...options,
23
- parallel: false,
24
- withDeps: false,
25
- spa: false,
26
- cors: true,
27
- }, context)
28
- : (0, dev_server_impl_1.default)(options, context);
29
- const p = context.projectsConfigurations.projects[context.projectName];
30
- const buildOptions = getBuildOptions(options.buildTarget, context);
31
- if (!options.isInitialHost) {
32
- return yield* currIter;
19
+ function startStaticRemotesFileServer(remotes, context, options) {
20
+ let shouldMoveToCommonLocation = false;
21
+ let commonOutputDirectory;
22
+ for (const app of remotes.staticRemotes) {
23
+ const outputPath = context.projectGraph.nodes[app].data.targets['build'].options.outputPath;
24
+ const directoryOfOutputPath = (0, path_1.dirname)(outputPath);
25
+ if (!commonOutputDirectory) {
26
+ commonOutputDirectory = directoryOfOutputPath;
27
+ }
28
+ else if (commonOutputDirectory !== directoryOfOutputPath) {
29
+ shouldMoveToCommonLocation = true;
30
+ }
33
31
  }
34
- const moduleFederationConfig = (0, module_federation_1.getModuleFederationConfig)(buildOptions.tsConfig, context.root, p.root, 'react');
35
- const remotes = (0, module_federation_1.getRemotes)(options.devRemotes, options.skipRemotes, moduleFederationConfig, {
36
- projectName: context.projectName,
37
- projectGraph: context.projectGraph,
38
- root: context.root,
39
- });
40
- devkit_1.logger.info(`NX Building ${remotes.staticRemotes.length} static remotes...`);
41
- await new Promise((res) => {
42
- const staticProcess = (0, child_process_1.fork)(nxBin, [
43
- 'run-many',
44
- `--target=build`,
45
- `--projects=${remotes.staticRemotes.join(',')}`,
46
- ...(context.configurationName
47
- ? [`--configuration=${context.configurationName}`]
48
- : []),
49
- ...(options.parallel ? [`--parallel=${options.parallel}`] : []),
50
- ], {
51
- cwd: context.root,
52
- stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
53
- });
54
- staticProcess.stdout.on('data', (data) => {
55
- const ANSII_CODE_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
56
- const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
57
- if (stdoutString.includes('Successfully ran target build')) {
58
- staticProcess.stdout.removeAllListeners('data');
59
- devkit_1.logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
60
- res();
61
- }
62
- });
63
- staticProcess.stderr.on('data', (data) => devkit_1.logger.info(data.toString()));
64
- staticProcess.on('exit', (code) => {
65
- if (code !== 0) {
66
- throw new Error(`Remote failed to start. See above for errors.`);
67
- }
68
- });
69
- process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
70
- process.on('exit', () => staticProcess.kill('SIGTERM'));
71
- });
72
- let isCollectingStaticRemoteOutput = true;
32
+ if (shouldMoveToCommonLocation) {
33
+ commonOutputDirectory = (0, path_1.join)(devkit_1.workspaceRoot, 'tmp/static-remotes');
34
+ for (const app of remotes.staticRemotes) {
35
+ const outputPath = context.projectGraph.nodes[app].data.targets['build'].options
36
+ .outputPath;
37
+ (0, fs_1.cpSync)(outputPath, commonOutputDirectory, {
38
+ force: true,
39
+ recursive: true,
40
+ });
41
+ }
42
+ }
43
+ const staticRemotesIter = (0, file_server_impl_1.default)({
44
+ cors: true,
45
+ watch: false,
46
+ staticFilePath: commonOutputDirectory,
47
+ parallel: false,
48
+ spa: false,
49
+ withDeps: false,
50
+ host: options.host,
51
+ port: options.staticRemotesPort,
52
+ ssl: options.ssl,
53
+ sslCert: options.sslCert,
54
+ sslKey: options.sslKey,
55
+ }, context);
56
+ return staticRemotesIter;
57
+ }
58
+ async function startDevRemotes(remotes, context) {
73
59
  const devRemoteIters = [];
74
60
  for (const app of remotes.devRemotes) {
75
61
  const remoteProjectServeTarget = context.projectGraph.nodes[app].data.targets['serve'];
@@ -85,40 +71,86 @@ async function* moduleFederationDevServer(options, context) {
85
71
  : {}),
86
72
  }, context));
87
73
  }
74
+ return devRemoteIters;
75
+ }
76
+ async function buildStaticRemotes(remotes, nxBin, context, options) {
77
+ devkit_1.logger.info(`NX Building ${remotes.staticRemotes.length} static remotes...`);
78
+ const mappedLocationOfRemotes = {};
88
79
  for (const app of remotes.staticRemotes) {
89
- const remoteProjectServeTarget = context.projectGraph.nodes[app].data.targets['serve-static'];
90
- const isUsingModuleFederationDevServerExecutor = remoteProjectServeTarget.executor.includes('module-federation-dev-server');
91
- let outWithErr = [];
80
+ mappedLocationOfRemotes[app] = `http${options.ssl ? 's' : ''}://${options.host}:${options.staticRemotesPort}/${app}`;
81
+ }
82
+ process.env.NX_MF_DEV_SERVER_STATIC_REMOTES = JSON.stringify(mappedLocationOfRemotes);
83
+ await new Promise((res) => {
92
84
  const staticProcess = (0, child_process_1.fork)(nxBin, [
93
- 'run',
94
- `${app}:serve-static${context.configurationName ? `:${context.configurationName}` : ''}`,
95
- ...(isUsingModuleFederationDevServerExecutor
96
- ? [`--isInitialHost=false`]
85
+ 'run-many',
86
+ `--target=build`,
87
+ `--projects=${remotes.staticRemotes.join(',')}`,
88
+ ...(context.configurationName
89
+ ? [`--configuration=${context.configurationName}`]
97
90
  : []),
91
+ ...(options.parallel ? [`--parallel=${options.parallel}`] : []),
98
92
  ], {
99
93
  cwd: context.root,
100
94
  stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
101
95
  });
102
96
  staticProcess.stdout.on('data', (data) => {
103
- if (isCollectingStaticRemoteOutput) {
104
- outWithErr.push(data.toString());
105
- }
106
- else {
107
- outWithErr = null;
97
+ const ANSII_CODE_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
98
+ const stdoutString = data.toString().replace(ANSII_CODE_REGEX, '');
99
+ if (stdoutString.includes('Successfully ran target build')) {
108
100
  staticProcess.stdout.removeAllListeners('data');
101
+ devkit_1.logger.info(`NX Built ${remotes.staticRemotes.length} static remotes`);
102
+ res();
109
103
  }
110
104
  });
111
105
  staticProcess.stderr.on('data', (data) => devkit_1.logger.info(data.toString()));
112
106
  staticProcess.on('exit', (code) => {
113
107
  if (code !== 0) {
114
- devkit_1.logger.info(outWithErr.join(''));
115
108
  throw new Error(`Remote failed to start. See above for errors.`);
116
109
  }
117
110
  });
118
111
  process.on('SIGTERM', () => staticProcess.kill('SIGTERM'));
119
112
  process.on('exit', () => staticProcess.kill('SIGTERM'));
113
+ });
114
+ }
115
+ async function* moduleFederationDevServer(options, context) {
116
+ const initialStaticRemotesPorts = options.staticRemotesPort;
117
+ options.staticRemotesPort ??= options.port + 1;
118
+ // Force Node to resolve to look for the nx binary that is inside node_modules
119
+ const nxBin = require.resolve('nx/bin/nx');
120
+ const currIter = options.static
121
+ ? (0, file_server_impl_1.default)({
122
+ ...options,
123
+ parallel: false,
124
+ withDeps: false,
125
+ spa: false,
126
+ cors: true,
127
+ }, context)
128
+ : (0, dev_server_impl_1.default)(options, context);
129
+ const p = context.projectsConfigurations.projects[context.projectName];
130
+ const buildOptions = getBuildOptions(options.buildTarget, context);
131
+ if (!options.isInitialHost) {
132
+ return yield* currIter;
133
+ }
134
+ const moduleFederationConfig = (0, module_federation_1.getModuleFederationConfig)(buildOptions.tsConfig, context.root, p.root, 'react');
135
+ const remotes = (0, module_federation_1.getRemotes)(options.devRemotes, options.skipRemotes, moduleFederationConfig, {
136
+ projectName: context.projectName,
137
+ projectGraph: context.projectGraph,
138
+ root: context.root,
139
+ });
140
+ if (remotes.devRemotes.length > 0 && !initialStaticRemotesPorts) {
141
+ options.staticRemotesPort = options.devRemotes.reduce((portToUse, r) => {
142
+ const remotePort = context.projectGraph.nodes[r].data.targets['serve'].options.port;
143
+ if (remotePort >= portToUse) {
144
+ return remotePort + 1;
145
+ }
146
+ }, options.staticRemotesPort);
120
147
  }
121
- return yield* (0, async_iterable_1.combineAsyncIterables)(currIter, ...devRemoteIters, (0, async_iterable_1.createAsyncIterable)(async ({ next, done }) => {
148
+ await buildStaticRemotes(remotes, nxBin, context, options);
149
+ const devRemoteIters = await startDevRemotes(remotes, context);
150
+ const staticRemotesIter = remotes.staticRemotes.length > 0
151
+ ? startStaticRemotesFileServer(remotes, context, options)
152
+ : undefined;
153
+ return yield* (0, async_iterable_1.combineAsyncIterables)(currIter, ...devRemoteIters, ...(staticRemotesIter ? [staticRemotesIter] : []), (0, async_iterable_1.createAsyncIterable)(async ({ next, done }) => {
122
154
  if (!options.isInitialHost) {
123
155
  done();
124
156
  return;
@@ -128,15 +160,14 @@ async function* moduleFederationDevServer(options, context) {
128
160
  return;
129
161
  }
130
162
  try {
131
- await Promise.all(remotes.remotePorts.map((port) =>
132
- // Allow 20 minutes for each remote to start, which is plenty of time but we can tweak it later if needed.
133
- // Most remotes should start in under 1 minute.
134
- (0, wait_for_port_open_1.waitForPortOpen)(port, {
163
+ const portsToWaitFor = staticRemotesIter
164
+ ? [options.staticRemotesPort, ...remotes.remotePorts]
165
+ : [...remotes.remotePorts];
166
+ await Promise.all(portsToWaitFor.map((port) => (0, wait_for_port_open_1.waitForPortOpen)(port, {
135
167
  retries: 480,
136
168
  retryDelay: 2500,
137
169
  host: 'localhost',
138
170
  })));
139
- isCollectingStaticRemoteOutput = false;
140
171
  devkit_1.logger.info(`NX All remotes started, server ready at http://localhost:${options.port}`);
141
172
  next({ success: true, baseUrl: `http://localhost:${options.port}` });
142
173
  }
@@ -105,6 +105,10 @@
105
105
  "parallel": {
106
106
  "type": "number",
107
107
  "description": "Max number of parallel processes for building static remotes"
108
+ },
109
+ "staticRemotesPort": {
110
+ "type": "number",
111
+ "description": "The port at which to serve the file-server for the static remotes."
108
112
  }
109
113
  }
110
114
  }
@@ -8,15 +8,14 @@ function updateSpecConfig(host, options) {
8
8
  return;
9
9
  }
10
10
  (0, devkit_1.updateJson)(host, `${options.appProjectRoot}/tsconfig.spec.json`, (json) => {
11
- json.types = json.types || [];
11
+ const compilerOptions = json.compilerOptions ?? {};
12
+ const types = compilerOptions.types ?? [];
12
13
  if (options.style === 'styled-jsx') {
13
- json.types.push('@nx/react/typings/styled-jsx.d.ts');
14
+ types.push('@nx/react/typings/styled-jsx.d.ts');
14
15
  }
15
- json.types = [
16
- ...json.types,
17
- '@nx/react/typings/cssmodule.d.ts',
18
- '@nx/react/typings/image.d.ts',
19
- ];
16
+ types.push('@nx/react/typings/cssmodule.d.ts', '@nx/react/typings/image.d.ts');
17
+ compilerOptions.types = types;
18
+ json.compilerOptions = compilerOptions;
20
19
  return json;
21
20
  });
22
21
  if (options.unitTestRunner !== 'jest') {
@@ -3,12 +3,20 @@ import * as React from 'react';
3
3
  import NxWelcome from "./nx-welcome";
4
4
  <% } %>
5
5
  import { Link, Route, Routes } from 'react-router-dom';
6
+ <% if (dynamic) { %>
7
+ import { loadRemoteModule } from '@nx/react/mf';
8
+ <% } %>
6
9
 
7
10
  <% if (remotes.length > 0) { %>
8
11
  <% remotes.forEach(function(r) { %>
12
+ <% if (dynamic) { %>
13
+ const <%= r.className %> = React.lazy(() => loadRemoteModule('<%= r.fileName %>', './Module'))
14
+ <% } else { %>
9
15
  const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
16
+ <% } %>
10
17
  <% }); %>
11
18
  <% } %>
19
+
12
20
  export function App() {
13
21
  return (
14
22
  <React.Suspense fallback={null}>
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "types": [
6
+ "node",
7
+ "@nx/react/typings/cssmodule.d.ts",
8
+ "@nx/react/typings/image.d.ts"
9
+ ]
10
+ },
11
+ "include": [
12
+ "src/**/*.js",
13
+ "src/**/*.jsx",
14
+ "src/**/*.ts",
15
+ "src/**/*.tsx",
16
+ "webpack.config.ts",
17
+ "webpack.prod.config.ts"
18
+ ]
19
+ }
@@ -1,6 +1,21 @@
1
1
  module.exports = {
2
2
  name: '<%= projectName %>',
3
+ /**
4
+ * To use a remote that does not exist in your current Nx Workspace
5
+ * You can use the tuple-syntax to define your remote
6
+ *
7
+ * remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']]
8
+ *
9
+ * You _may_ need to add a `remotes.d.ts` file to your `src/` folder declaring the external remote for tsc, with the
10
+ * following content:
11
+ *
12
+ * declare module 'my-external-remote';
13
+ *
14
+ */
3
15
  remotes: [
4
- <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
16
+ <% if (static) {
17
+ remotes.forEach(function(r) { %> "<%= r.fileName %>", <% });
18
+ }
19
+ %>
5
20
  ],
6
- };
21
+ };
@@ -1 +1,10 @@
1
- import('./bootstrap');
1
+ <% if (dynamic) { %>
2
+ import { setRemoteDefinitions } from '@nx/react/mf';
3
+
4
+ fetch('/assets/module-federation.manifest.json')
5
+ .then((res) => res.json())
6
+ .then(definitions => setRemoteDefinitions(definitions))
7
+ .then(() => import('./bootstrap').catch(err => console.error(err)));
8
+ <% } else { %>
9
+ import('./bootstrap').catch(err => console.error(err));
10
+ <% } %>
@@ -6,8 +6,11 @@
6
6
  const moduleFederationConfig = {
7
7
  name: '<%= projectName %>',
8
8
  remotes: [
9
- <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
10
- ],
9
+ <% if (static) {
10
+ remotes.forEach(function(r) { %> "<%= r.fileName %>", <% });
11
+ }
12
+ %>
13
+ ],
11
14
  };
12
15
 
13
16
  module.exports = moduleFederationConfig;
@@ -3,8 +3,11 @@ import { ModuleFederationConfig } from '@nx/webpack';
3
3
  const config: ModuleFederationConfig = {
4
4
  name: '<%= projectName %>',
5
5
  remotes: [
6
- <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
7
- ],
6
+ <% if (static) {
7
+ remotes.forEach(function(r) { %> "<%= r.fileName %>", <% });
8
+ }
9
+ %>
10
+ ],
8
11
  };
9
12
 
10
13
  export default config;
@@ -2,9 +2,24 @@ import { ModuleFederationConfig } from '@nx/webpack';
2
2
 
3
3
  const config: ModuleFederationConfig = {
4
4
  name: '<%= projectName %>',
5
+ /**
6
+ * To use a remote that does not exist in your current Nx Workspace
7
+ * You can use the tuple-syntax to define your remote
8
+ *
9
+ * remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']]
10
+ *
11
+ * You _may_ need to add a `remotes.d.ts` file to your `src/` folder declaring the external remote for tsc, with the
12
+ * following content:
13
+ *
14
+ * declare module 'my-external-remote';
15
+ *
16
+ */
5
17
  remotes: [
6
- <% remotes.forEach(function(r) {%> "<%= r.fileName %>", <% }); %>
7
- ],
18
+ <% if (static) {
19
+ remotes.forEach(function(r) { %> "<%= r.fileName %>", <% });
20
+ }
21
+ %>
22
+ ],
8
23
  };
9
24
 
10
25
  export default config;
@@ -1 +1,10 @@
1
- import('./bootstrap');
1
+ <% if (dynamic) { %>
2
+ import { setRemoteDefinitions } from '@nx/react/mf';
3
+
4
+ fetch('/assets/module-federation.manifest.json')
5
+ .then((res) => res.json())
6
+ .then(definitions => setRemoteDefinitions(definitions))
7
+ .then(() => import('./bootstrap').catch(err => console.error(err)));
8
+ <% } else { %>
9
+ import('./bootstrap').catch(err => console.error(err));
10
+ <% } %>
@@ -23,6 +23,7 @@ async function hostGeneratorInternal(host, schema) {
23
23
  const options = {
24
24
  ...(await (0, normalize_options_1.normalizeOptions)(host, schema, '@nx/react:host')),
25
25
  typescriptConfiguration: schema.typescriptConfiguration ?? true,
26
+ dynamic: schema.dynamic ?? false,
26
27
  };
27
28
  const initTask = await (0, application_1.default)(host, {
28
29
  ...options,
@@ -51,6 +52,8 @@ async function hostGeneratorInternal(host, schema) {
51
52
  skipFormat: true,
52
53
  projectNameAndRootFormat: options.projectNameAndRootFormat,
53
54
  typescriptConfiguration: options.typescriptConfiguration,
55
+ dynamic: options.dynamic,
56
+ host: options.name,
54
57
  });
55
58
  tasks.push(remoteTask);
56
59
  remotePort++;
@@ -72,6 +75,9 @@ async function hostGeneratorInternal(host, schema) {
72
75
  projectConfig.targets.server.options.webpackConfig = (0, devkit_1.joinPathFragments)(projectConfig.root, `webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`);
73
76
  (0, devkit_1.updateProjectConfiguration)(host, options.projectName, projectConfig);
74
77
  }
78
+ if (!options.setParserOptionsProject) {
79
+ host.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.lint.json'));
80
+ }
75
81
  if (!options.skipFormat) {
76
82
  await (0, devkit_1.formatFiles)(host);
77
83
  }
@@ -1,5 +1,6 @@
1
1
  import { NormalizedSchema } from '../schema';
2
- export declare function addModuleFederationFiles(host: any, options: NormalizedSchema, defaultRemoteManifest: {
2
+ import { Tree } from '@nx/devkit';
3
+ export declare function addModuleFederationFiles(host: Tree, options: NormalizedSchema, defaultRemoteManifest: {
3
4
  name: string;
4
5
  port: number;
5
6
  }[]): void;
@@ -2,11 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.addModuleFederationFiles = void 0;
4
4
  const devkit_1 = require("@nx/devkit");
5
- const path_1 = require("path");
6
5
  function addModuleFederationFiles(host, options, defaultRemoteManifest) {
7
6
  const templateVariables = {
8
7
  ...(0, devkit_1.names)(options.name),
9
8
  ...options,
9
+ static: !options?.dynamic,
10
10
  tmpl: '',
11
11
  remotes: defaultRemoteManifest.map(({ name, port }) => {
12
12
  return {
@@ -15,24 +15,36 @@ function addModuleFederationFiles(host, options, defaultRemoteManifest) {
15
15
  };
16
16
  }),
17
17
  };
18
+ const projectConfig = (0, devkit_1.readProjectConfiguration)(host, options.name);
19
+ const pathToMFManifest = (0, devkit_1.joinPathFragments)(projectConfig.sourceRoot, 'assets/module-federation.manifest.json');
18
20
  // Module federation requires bootstrap code to be dynamically imported.
19
21
  // Renaming original entry file so we can use `import(./bootstrap)` in
20
22
  // new entry file.
21
- host.rename((0, path_1.join)(options.appProjectRoot, 'src/main.tsx'), (0, path_1.join)(options.appProjectRoot, 'src/bootstrap.tsx'));
22
- (0, devkit_1.generateFiles)(host, (0, path_1.join)(__dirname, `../files/common`), options.appProjectRoot, templateVariables);
23
+ host.rename((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/main.tsx'), (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/bootstrap.tsx'));
24
+ (0, devkit_1.generateFiles)(host, (0, devkit_1.joinPathFragments)(__dirname, `../files/common`), options.appProjectRoot, templateVariables);
23
25
  const pathToModuleFederationFiles = options.typescriptConfiguration
24
26
  ? 'module-federation-ts'
25
27
  : 'module-federation';
26
28
  // New entry file is created here.
27
- (0, devkit_1.generateFiles)(host, (0, path_1.join)(__dirname, `../files/${pathToModuleFederationFiles}`), options.appProjectRoot, templateVariables);
28
- if (options.typescriptConfiguration) {
29
- const pathToWebpackConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js');
30
- const pathToWebpackProdConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.prod.js');
31
- if (host.exists(pathToWebpackConfig)) {
32
- host.delete(pathToWebpackConfig);
29
+ (0, devkit_1.generateFiles)(host, (0, devkit_1.joinPathFragments)(__dirname, `../files/${pathToModuleFederationFiles}`), options.appProjectRoot, templateVariables);
30
+ function deleteFileIfExists(host, filePath) {
31
+ if (host.exists(filePath)) {
32
+ host.delete(filePath);
33
33
  }
34
- if (host.exists(pathToWebpackProdConfig)) {
35
- host.delete(pathToWebpackProdConfig);
34
+ }
35
+ function processWebpackConfig(options, host, fileName) {
36
+ const pathToWebpackConfig = (0, devkit_1.joinPathFragments)(options.appProjectRoot, fileName);
37
+ deleteFileIfExists(host, pathToWebpackConfig);
38
+ }
39
+ if (options.typescriptConfiguration) {
40
+ processWebpackConfig(options, host, 'webpack.config.js');
41
+ processWebpackConfig(options, host, 'webpack.config.prod.js');
42
+ }
43
+ if (options.dynamic) {
44
+ processWebpackConfig(options, host, 'webpack.config.prod.js');
45
+ processWebpackConfig(options, host, 'webpack.config.prod.ts');
46
+ if (!host.exists(pathToMFManifest)) {
47
+ host.write(pathToMFManifest, '{}');
36
48
  }
37
49
  }
38
50
  }
@@ -13,6 +13,7 @@ async function setupSsrForHost(tree, options, appName, defaultRemoteManifest) {
13
13
  : 'module-federation-ssr';
14
14
  (0, devkit_1.generateFiles)(tree, (0, devkit_1.joinPathFragments)(__dirname, `../files/${pathToModuleFederationSsrFiles}`), project.root, {
15
15
  ...options,
16
+ static: !options?.dynamic,
16
17
  remotes: defaultRemoteManifest.map(({ name, port }) => {
17
18
  return {
18
19
  ...(0, devkit_1.names)(name),
@@ -25,6 +25,7 @@ export interface Schema {
25
25
  unitTestRunner: 'jest' | 'vitest' | 'none';
26
26
  minimal?: boolean;
27
27
  typescriptConfiguration?: boolean;
28
+ dynamic?: boolean;
28
29
  }
29
30
 
30
31
  export interface NormalizedSchema extends Schema {
@@ -74,6 +74,11 @@
74
74
  "enum": ["eslint"],
75
75
  "default": "eslint"
76
76
  },
77
+ "dynamic": {
78
+ "type": "boolean",
79
+ "description": "Should the host application use dynamic federation?",
80
+ "default": false
81
+ },
77
82
  "skipFormat": {
78
83
  "description": "Skip formatting files.",
79
84
  "type": "boolean",
@@ -48,8 +48,8 @@ async function reactInitGenerator(host, schema) {
48
48
  setDefault(host);
49
49
  if (!schema.e2eTestRunner || schema.e2eTestRunner === 'cypress') {
50
50
  (0, devkit_1.ensurePackage)('@nx/cypress', versions_1.nxVersion);
51
- const { cypressInitGenerator } = await Promise.resolve().then(() => require('@nx/cypress/src/generators/init/init'));
52
- const cypressTask = await cypressInitGenerator(host, {});
51
+ const { cypressInitGenerator } = (await Promise.resolve().then(() => require('@nx/cypress/src/generators/init/init')));
52
+ const cypressTask = await cypressInitGenerator(host, schema);
53
53
  tasks.push(cypressTask);
54
54
  }
55
55
  if (!schema.skipPackageJson) {
@@ -1,5 +1,8 @@
1
1
  module.exports = {
2
2
  name: '<%= projectName %>',
3
+ <% if (dynamic) { %>
4
+ library: { type: 'var', name: '<%= projectName %>'},
5
+ <% } %>
3
6
  exposes: {
4
7
  './Module': './src/remote-entry.ts',
5
8
  },
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "types": [
6
+ "node",
7
+ "@nx/react/typings/cssmodule.d.ts",
8
+ "@nx/react/typings/image.d.ts"
9
+ ]
10
+ },
11
+ "include": [
12
+ "src/**/*.js",
13
+ "src/**/*.jsx",
14
+ "src/**/*.ts",
15
+ "src/**/*.tsx",
16
+ "webpack.config.ts",
17
+ "webpack.prod.config.ts"
18
+ ]
19
+ }
@@ -2,6 +2,9 @@ import {ModuleFederationConfig} from '@nx/webpack';
2
2
 
3
3
  const config: ModuleFederationConfig = {
4
4
  name: '<%= projectName %>',
5
+ <% if (dynamic) { %>
6
+ library: { type: 'var', name: '<%= projectName %>'},
7
+ <% } %>
5
8
  exposes: {
6
9
  './Module': './src/remote-entry.ts',
7
10
  },
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "types": [
6
+ "node",
7
+ "@nx/react/typings/cssmodule.d.ts",
8
+ "@nx/react/typings/image.d.ts"
9
+ ]
10
+ },
11
+ "include": [
12
+ "src/**/*.js",
13
+ "src/**/*.jsx",
14
+ "src/**/*.ts",
15
+ "src/**/*.tsx",
16
+ "webpack.config.ts",
17
+ "webpack.prod.config.ts"
18
+ ]
19
+ }
@@ -0,0 +1,2 @@
1
+ import { Tree } from '@nx/devkit';
2
+ export declare function addRemoteToDynamicHost(tree: Tree, remoteName: string, remotePort: number, pathToMfManifest: string): void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addRemoteToDynamicHost = void 0;
4
+ function addRemoteToDynamicHost(tree, remoteName, remotePort, pathToMfManifest) {
5
+ const current = tree.read(pathToMfManifest, 'utf8');
6
+ tree.write(pathToMfManifest, JSON.stringify({
7
+ ...JSON.parse(current),
8
+ [remoteName]: `http://localhost:${remotePort}`,
9
+ }));
10
+ }
11
+ exports.addRemoteToDynamicHost = addRemoteToDynamicHost;
@@ -10,6 +10,7 @@ const update_module_federation_project_1 = require("../../rules/update-module-fe
10
10
  const setup_ssr_1 = require("../setup-ssr/setup-ssr");
11
11
  const setup_ssr_for_remote_1 = require("./lib/setup-ssr-for-remote");
12
12
  const setup_tspath_for_remote_1 = require("./lib/setup-tspath-for-remote");
13
+ const add_remote_to_dynamic_host_1 = require("./lib/add-remote-to-dynamic-host");
13
14
  function addModuleFederationFiles(host, options) {
14
15
  const templateVariables = {
15
16
  ...(0, devkit_1.names)(options.name),
@@ -44,6 +45,7 @@ async function remoteGeneratorInternal(host, schema) {
44
45
  const options = {
45
46
  ...(await (0, normalize_options_1.normalizeOptions)(host, schema, '@nx/react:remote')),
46
47
  typescriptConfiguration: schema.typescriptConfiguration ?? false,
48
+ dynamic: schema.dynamic ?? false,
47
49
  };
48
50
  const initAppTask = await (0, application_1.default)(host, {
49
51
  ...options,
@@ -75,6 +77,14 @@ async function remoteGeneratorInternal(host, schema) {
75
77
  projectConfig.targets.server.options.webpackConfig = (0, devkit_1.joinPathFragments)(projectConfig.root, `webpack.server.config.${options.typescriptConfiguration ? 'ts' : 'js'}`);
76
78
  (0, devkit_1.updateProjectConfiguration)(host, options.projectName, projectConfig);
77
79
  }
80
+ if (!options.setParserOptionsProject) {
81
+ host.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.lint.json'));
82
+ }
83
+ if (options.host && options.dynamic) {
84
+ const hostConfig = (0, devkit_1.readProjectConfiguration)(host, schema.host);
85
+ const pathToMFManifest = (0, devkit_1.joinPathFragments)(hostConfig.sourceRoot, 'assets/module-federation.manifest.json');
86
+ (0, add_remote_to_dynamic_host_1.addRemoteToDynamicHost)(host, options.name, options.devServerPort, pathToMFManifest);
87
+ }
78
88
  if (!options.skipFormat) {
79
89
  await (0, devkit_1.formatFiles)(host);
80
90
  }
@@ -26,6 +26,7 @@ export interface Schema {
26
26
  tags?: string;
27
27
  unitTestRunner: 'jest' | 'vitest' | 'none';
28
28
  typescriptConfiguration?: boolean;
29
+ dynamic?: boolean;
29
30
  }
30
31
 
31
32
  export interface NormalizedSchema extends ApplicationNormalizedSchema {
@@ -28,6 +28,12 @@
28
28
  "type": "string",
29
29
  "enum": ["as-provided", "derived"]
30
30
  },
31
+ "dynamic": {
32
+ "type": "boolean",
33
+ "description": "Should the host application use dynamic federation?",
34
+ "default": false,
35
+ "x-priority": "internal"
36
+ },
31
37
  "style": {
32
38
  "description": "The file extension to be used for style files.",
33
39
  "type": "string",
@@ -8,6 +8,13 @@ function getFunctionDeterminateRemoteUrl(isServer = false) {
8
8
  const target = isServer ? 'serve-server' : 'serve';
9
9
  const remoteEntry = isServer ? 'server/remoteEntry.js' : 'remoteEntry.js';
10
10
  return function (remote) {
11
+ const mappedStaticRemotesFromEnv = process.env
12
+ .NX_MF_DEV_SERVER_STATIC_REMOTES
13
+ ? JSON.parse(process.env.NX_MF_DEV_SERVER_STATIC_REMOTES)
14
+ : undefined;
15
+ if (mappedStaticRemotesFromEnv && mappedStaticRemotesFromEnv[remote]) {
16
+ return `${mappedStaticRemotesFromEnv[remote]}/${remoteEntry}`;
17
+ }
11
18
  let remoteConfiguration = null;
12
19
  try {
13
20
  remoteConfiguration = (0, project_graph_1.readCachedProjectConfiguration)(remote);
@@ -4,4 +4,5 @@ export declare function updateModuleFederationProject(host: Tree, options: {
4
4
  appProjectRoot: string;
5
5
  devServerPort?: number;
6
6
  typescriptConfiguration?: boolean;
7
+ dynamic?: boolean;
7
8
  }): GeneratorCallback;
@@ -14,6 +14,14 @@ function updateModuleFederationProject(host, options) {
14
14
  ...projectConfig.targets.build.configurations.production,
15
15
  webpackConfig: `${options.appProjectRoot}/webpack.config.prod.${options.typescriptConfiguration ? 'ts' : 'js'}`,
16
16
  };
17
+ // If host should be configured to use dynamic federation
18
+ if (options.dynamic) {
19
+ const pathToProdWebpackConfig = (0, devkit_1.joinPathFragments)(projectConfig.root, `webpack.prod.config.${options.typescriptConfiguration ? 'ts' : 'js'}`);
20
+ if (host.exists(pathToProdWebpackConfig)) {
21
+ host.delete(pathToProdWebpackConfig);
22
+ }
23
+ delete projectConfig.targets.build.configurations.production?.webpackConfig;
24
+ }
17
25
  projectConfig.targets.serve.executor =
18
26
  '@nx/react:module-federation-dev-server';
19
27
  projectConfig.targets.serve.options.port = options.devServerPort;