Skip to content

Commit 94a37ba

Browse files
committed
feat(module-federation): use angular-specific dependencies lookup
1 parent 8fae147 commit 94a37ba

13 files changed

+542
-18
lines changed

packages/angular/src/generators/convert-to-rspack/convert-to-rspack.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ describe('convert-to-rspack', () => {
462462
"const {
463463
NxModuleFederationPlugin,
464464
NxModuleFederationDevServerPlugin,
465-
} = require('@nx/module-federation/rspack');
465+
} = require('@nx/module-federation/angular');
466466
const config = require('./module-federation.config');
467467
468468
module.exports = {

packages/angular/src/generators/convert-to-rspack/convert-to-rspack.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,7 @@ const RENAMED_OPTIONS = {
4848

4949
const DEFAULT_PORT = 4200;
5050

51-
const REMOVED_OPTIONS = [
52-
'buildOptimizer',
53-
'buildTarget',
54-
'browserTarget',
55-
'publicHost',
56-
];
51+
const REMOVED_OPTIONS = ['buildOptimizer', 'buildTarget', 'browserTarget'];
5752

5853
function normalizeFromProjectRoot(
5954
tree: Tree,

packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
1616
// ASSERT
1717
expect(newWebpackConfigContents).toMatchInlineSnapshot(`
1818
"
19-
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';
19+
import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/angular';
2020
import config from './module-federation.config';
2121
2222
@@ -47,7 +47,7 @@ describe('convertconvertWebpackConfigToUseNxModuleFederationPlugin', () => {
4747
// ASSERT
4848
expect(newWebpackConfigContents).toMatchInlineSnapshot(`
4949
"
50-
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/rspack');
50+
const { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } = require('@nx/module-federation/angular');
5151
const config = require('./module-federation.config');
5252
5353

packages/angular/src/generators/convert-to-rspack/lib/get-custom-webpack-config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function convertWebpackConfigToUseNxModuleFederationPlugin(
5656
newWebpackConfigContents = `${webpackConfigContents.slice(
5757
0,
5858
withModuleFederationImportNode.getStart()
59-
)}import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/rspack';${webpackConfigContents.slice(
59+
)}import { NxModuleFederationPlugin, NxModuleFederationDevServerPlugin } from '@nx/module-federation/angular';${webpackConfigContents.slice(
6060
withModuleFederationImportNode.getEnd()
6161
)}`;
6262

packages/angular/src/generators/host/host.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ export async function host(tree: Tree, schema: Schema) {
112112
installTasks.push(ssrInstallTask);
113113
}
114114

115-
for (const remote of remotesToGenerate) {
115+
for (let i = 0; i < remotesToGenerate.length; i++) {
116+
const remote = remotesToGenerate[i];
116117
const remoteDirectory = options.directory
117118
? joinPathFragments(options.directory, '..', remote)
118119
: appRoot === '.'
@@ -123,6 +124,7 @@ export async function host(tree: Tree, schema: Schema) {
123124
name: remote,
124125
directory: remoteDirectory,
125126
host: hostProjectName,
127+
port: isRspack ? 4200 + i + 1 : undefined,
126128
skipFormat: true,
127129
standalone: options.standalone,
128130
typescriptConfiguration,

packages/module-federation/angular.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
export * from './src/with-module-federation/angular/with-module-federation';
22
export * from './src/with-module-federation/angular/with-module-federation-ssr';
3+
export * from './src/plugins/nx-module-federation-plugin/angular/nx-module-federation-plugin';
4+
export * from './src/plugins/nx-module-federation-plugin/angular/nx-module-federation-dev-server-plugin';
5+
export * from './src/plugins/nx-module-federation-plugin/angular/nx-module-federation-ssr-dev-server-plugin';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import {
2+
Compilation,
3+
Compiler,
4+
DefinePlugin,
5+
RspackPluginInstance,
6+
} from '@rspack/core';
7+
import * as pc from 'picocolors';
8+
import {
9+
logger,
10+
readCachedProjectGraph,
11+
readProjectsConfigurationFromProjectGraph,
12+
workspaceRoot,
13+
} from '@nx/devkit';
14+
import { ModuleFederationConfig } from '../../../utils/models';
15+
import { extname, join } from 'path';
16+
import { existsSync } from 'fs';
17+
import {
18+
buildStaticRemotes,
19+
getDynamicMfManifestFile,
20+
getRemotes,
21+
getStaticRemotes,
22+
parseRemotesConfig,
23+
startRemoteProxies,
24+
startStaticRemotesFileServer,
25+
} from '../../utils';
26+
import { NxModuleFederationDevServerConfig } from '../../models';
27+
28+
const PLUGIN_NAME = 'NxModuleFederationDevServerPlugin';
29+
30+
export class NxModuleFederationDevServerPlugin implements RspackPluginInstance {
31+
private nxBin = require.resolve('nx/bin/nx');
32+
33+
constructor(
34+
private _options: {
35+
config: ModuleFederationConfig;
36+
devServerConfig?: NxModuleFederationDevServerConfig;
37+
}
38+
) {
39+
this._options.devServerConfig ??= {
40+
host: 'localhost',
41+
};
42+
}
43+
44+
apply(compiler: Compiler) {
45+
const isDevServer = process.env['WEBPACK_SERVE'];
46+
if (!isDevServer) {
47+
return;
48+
}
49+
compiler.hooks.watchRun.tapAsync(
50+
PLUGIN_NAME,
51+
async (compiler, callback) => {
52+
compiler.hooks.beforeCompile.tapAsync(
53+
PLUGIN_NAME,
54+
async (params, callback) => {
55+
const staticRemotesConfig = await this.setup();
56+
57+
logger.info(
58+
`NX Starting module federation dev-server for ${pc.bold(
59+
this._options.config.name
60+
)} with ${Object.keys(staticRemotesConfig).length} remotes`
61+
);
62+
63+
const mappedLocationOfRemotes = await buildStaticRemotes(
64+
staticRemotesConfig,
65+
this._options.devServerConfig,
66+
this.nxBin
67+
);
68+
startStaticRemotesFileServer(
69+
staticRemotesConfig,
70+
workspaceRoot,
71+
this._options.devServerConfig.staticRemotesPort
72+
);
73+
startRemoteProxies(staticRemotesConfig, mappedLocationOfRemotes, {
74+
pathToCert: this._options.devServerConfig.sslCert,
75+
pathToKey: this._options.devServerConfig.sslCert,
76+
});
77+
78+
new DefinePlugin({
79+
'process.env.NX_MF_DEV_REMOTES': process.env.NX_MF_DEV_REMOTES,
80+
}).apply(compiler);
81+
82+
callback();
83+
}
84+
);
85+
callback();
86+
}
87+
);
88+
}
89+
90+
private async setup() {
91+
const projectGraph = readCachedProjectGraph();
92+
const { projects: workspaceProjects } =
93+
readProjectsConfigurationFromProjectGraph(projectGraph);
94+
const project = workspaceProjects[this._options.config.name];
95+
if (!this._options.devServerConfig.pathToManifestFile) {
96+
this._options.devServerConfig.pathToManifestFile =
97+
getDynamicMfManifestFile(project, workspaceRoot);
98+
} else {
99+
const userPathToManifestFile = join(
100+
workspaceRoot,
101+
this._options.devServerConfig.pathToManifestFile
102+
);
103+
if (!existsSync(userPathToManifestFile)) {
104+
throw new Error(
105+
`The provided Module Federation manifest file path does not exist. Please check the file exists at "${userPathToManifestFile}".`
106+
);
107+
} else if (
108+
extname(this._options.devServerConfig.pathToManifestFile) !== '.json'
109+
) {
110+
throw new Error(
111+
`The Module Federation manifest file must be a JSON. Please ensure the file at ${userPathToManifestFile} is a JSON.`
112+
);
113+
}
114+
115+
this._options.devServerConfig.pathToManifestFile = userPathToManifestFile;
116+
}
117+
118+
const { remotes, staticRemotePort } = getRemotes(
119+
this._options.config,
120+
projectGraph,
121+
this._options.devServerConfig.pathToManifestFile
122+
);
123+
this._options.devServerConfig.staticRemotesPort ??= staticRemotePort;
124+
125+
const remotesConfig = parseRemotesConfig(
126+
remotes,
127+
workspaceRoot,
128+
projectGraph
129+
);
130+
const staticRemotesConfig = await getStaticRemotes(
131+
remotesConfig.config ?? {},
132+
this._options.devServerConfig?.devRemoteFindOptions,
133+
this._options.devServerConfig?.host
134+
);
135+
const devRemotes = remotes.filter((r) => !staticRemotesConfig[r]);
136+
process.env.NX_MF_DEV_REMOTES = JSON.stringify([
137+
...(devRemotes.length > 0 ? devRemotes : []),
138+
project.name,
139+
]);
140+
return staticRemotesConfig ?? {};
141+
}
142+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Compiler, RspackPluginInstance } from '@rspack/core';
2+
import {
3+
ModuleFederationConfig,
4+
NxModuleFederationConfigOverride,
5+
} from '../../../utils/models';
6+
import { getModuleFederationConfigSync } from '../../../with-module-federation/angular/utils';
7+
8+
export class NxModuleFederationPlugin implements RspackPluginInstance {
9+
constructor(
10+
private _options: {
11+
config: ModuleFederationConfig;
12+
isServer?: boolean;
13+
},
14+
private configOverride?: NxModuleFederationConfigOverride
15+
) {}
16+
17+
apply(compiler: Compiler) {
18+
if (global.NX_GRAPH_CREATION) {
19+
return;
20+
}
21+
22+
// This is required to ensure Module Federation will build the project correctly
23+
compiler.options.optimization ??= {};
24+
compiler.options.optimization.runtimeChunk = false;
25+
compiler.options.output.publicPath = !compiler.options.output.publicPath
26+
? 'auto'
27+
: compiler.options.output.publicPath;
28+
compiler.options.output.uniqueName = this._options.config.name;
29+
if (compiler.options.output.scriptType === 'module') {
30+
compiler.options.output.scriptType = undefined;
31+
compiler.options.output.module = undefined;
32+
}
33+
if (this._options.isServer) {
34+
compiler.options.target = 'async-node';
35+
compiler.options.output.library ??= {
36+
type: 'commonjs-module',
37+
};
38+
compiler.options.output.library.type = 'commonjs-module';
39+
}
40+
41+
const config = getModuleFederationConfigSync(
42+
this._options.config,
43+
{
44+
isServer: this._options.isServer,
45+
},
46+
true
47+
);
48+
const sharedLibraries = config.sharedLibraries;
49+
const sharedDependencies = config.sharedDependencies;
50+
const mappedRemotes = config.mappedRemotes;
51+
52+
const runtimePlugins = [];
53+
if (this.configOverride?.runtimePlugins) {
54+
runtimePlugins.push(...(this.configOverride.runtimePlugins ?? []));
55+
}
56+
if (this._options.isServer) {
57+
runtimePlugins.push(
58+
require.resolve('@module-federation/node/runtimePlugin')
59+
);
60+
}
61+
62+
new (require('@module-federation/enhanced/rspack').ModuleFederationPlugin)({
63+
name: this._options.config.name.replace(/-/g, '_'),
64+
filename: 'remoteEntry.js',
65+
exposes: this._options.config.exposes,
66+
remotes: mappedRemotes,
67+
shared: {
68+
...(sharedDependencies ?? {}),
69+
},
70+
...(this._options.isServer
71+
? {
72+
library: {
73+
type: 'commonjs-module',
74+
},
75+
remoteType: 'script',
76+
}
77+
: {}),
78+
...(this.configOverride ? this.configOverride : {}),
79+
runtimePlugins,
80+
virtualRuntimeEntry: true,
81+
}).apply(compiler);
82+
83+
if (sharedLibraries) {
84+
sharedLibraries.getReplacementPlugin().apply(compiler as any);
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)