feat(module-federation): use module-federation runtime for dynamic federation (#28704)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> We currently have hombrewed support for Dynamic Module Federation. However, Module Federation 2.0 comes with more powerful helpers to handle dynamic federation. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> For new host projects using dynamic federation, use the Module Federation Runtime. For existing hosts using Nx's dynamic federation, continue to use it when adding new remotes to it. Deprecate Nx's dynamic federation helpers with intended removal in Nx 22 ### Example Screenshot of Deprecation Message <img width="810" alt="image" src="https://github.com/user-attachments/assets/6c6a9504-6d89-497b-9259-9272b3f47276"> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
ab8d77e719
commit
0f25b3c42e
@ -1189,7 +1189,7 @@ describe('React Rspack Module Federation', () => {
|
||||
`${shell}/src/assets/module-federation.manifest.json`,
|
||||
(json) => {
|
||||
return {
|
||||
[remote]: `http://localhost:${remotePort}`,
|
||||
[remote]: `http://localhost:${remotePort}/mf-manifest.json`,
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -1198,7 +1198,9 @@ describe('React Rspack Module Federation', () => {
|
||||
`${shell}/src/assets/module-federation.manifest.json`
|
||||
);
|
||||
expect(manifest[remote]).toBeDefined();
|
||||
expect(manifest[remote]).toEqual('http://localhost:4205');
|
||||
expect(manifest[remote]).toEqual(
|
||||
'http://localhost:4205/mf-manifest.json'
|
||||
);
|
||||
|
||||
// update e2e
|
||||
updateFile(
|
||||
|
||||
@ -1015,7 +1015,7 @@ describe('React Module Federation', () => {
|
||||
`${shell}/src/assets/module-federation.manifest.json`,
|
||||
(json) => {
|
||||
return {
|
||||
[remote]: `http://localhost:${remotePort}`,
|
||||
[remote]: `http://localhost:${remotePort}/mf-manifest.json`,
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -1024,7 +1024,9 @@ describe('React Module Federation', () => {
|
||||
`${shell}/src/assets/module-federation.manifest.json`
|
||||
);
|
||||
expect(manifest[remote]).toBeDefined();
|
||||
expect(manifest[remote]).toEqual('http://localhost:4205');
|
||||
expect(manifest[remote]).toEqual(
|
||||
'http://localhost:4205/mf-manifest.json'
|
||||
);
|
||||
|
||||
// update e2e
|
||||
updateFile(
|
||||
|
||||
@ -7,6 +7,9 @@ declare const __webpack_share_scopes__: { default: unknown };
|
||||
|
||||
let resolveRemoteUrl: ResolveRemoteUrlFunction;
|
||||
|
||||
/**
|
||||
* @deprecated Use Runtime Helpers from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
*/
|
||||
export function setRemoteUrlResolver(
|
||||
_resolveRemoteUrl: ResolveRemoteUrlFunction
|
||||
) {
|
||||
@ -15,10 +18,56 @@ export function setRemoteUrlResolver(
|
||||
|
||||
let remoteUrlDefinitions: Record<string, string>;
|
||||
|
||||
/**
|
||||
* @deprecated Use init() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you have a remote app called `my-remote-app` and you want to use the `http://localhost:4201/mf-manifest.json` as the remote url, you should change it from:
|
||||
* ```ts
|
||||
* import { setRemoteDefinitions } from '@nx/angular/mf';
|
||||
*
|
||||
* setRemoteDefinitions({
|
||||
* 'my-remote-app': 'http://localhost:4201/mf-manifest.json'
|
||||
* });
|
||||
* ```
|
||||
* to use init():
|
||||
* ```ts
|
||||
* import { init } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* init({
|
||||
* name: 'host',
|
||||
* remotes: [{
|
||||
* name: 'my-remote-app',
|
||||
* entry: 'http://localhost:4201/mf-manifest.json'
|
||||
* }]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function setRemoteDefinitions(definitions: Record<string, string>) {
|
||||
remoteUrlDefinitions = definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use registerRemotes() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you set a remote app with `setRemoteDefinition` such as:
|
||||
* ```ts
|
||||
* import { setRemoteDefinition } from '@nx/angular/mf';
|
||||
*
|
||||
* setRemoteDefinition(
|
||||
* 'my-remote-app',
|
||||
* 'http://localhost:4201/mf-manifest.json'
|
||||
* );
|
||||
* ```
|
||||
* change it to use registerRemotes():
|
||||
* ```ts
|
||||
* import { registerRemotes } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* registerRemotes([
|
||||
* {
|
||||
* name: 'my-remote-app',
|
||||
* entry: 'http://localhost:4201/mf-manifest.json'
|
||||
* }
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
export function setRemoteDefinition(remoteName: string, remoteUrl: string) {
|
||||
remoteUrlDefinitions ??= {};
|
||||
remoteUrlDefinitions[remoteName] = remoteUrl;
|
||||
@ -27,6 +76,21 @@ export function setRemoteDefinition(remoteName: string, remoteUrl: string) {
|
||||
let remoteModuleMap = new Map<string, unknown>();
|
||||
let remoteContainerMap = new Map<string, unknown>();
|
||||
|
||||
/**
|
||||
* @deprecated Use loadRemote() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you set a load a remote with `loadRemoteModule` such as:
|
||||
* ```ts
|
||||
* import { loadRemoteModule } from '@nx/angular/mf';
|
||||
*
|
||||
* loadRemoteModule('my-remote-app', './Module').then(m => m.RemoteEntryModule);
|
||||
* ```
|
||||
* change it to use loadRemote():
|
||||
* ```ts
|
||||
* import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* loadRemote<typeof import('my-remote-app/Module')>('my-remote-app/Module').then(m => m.RemoteEntryModule);
|
||||
* ```
|
||||
*/
|
||||
export async function loadRemoteModule(remoteName: string, moduleName: string) {
|
||||
const remoteModuleKey = `${remoteName}:${moduleName}`;
|
||||
if (remoteModuleMap.has(remoteModuleKey)) {
|
||||
|
||||
@ -1,32 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Init MF --federationType=dynamic should create a host with the correct configurations 1`] = `
|
||||
"import { setRemoteDefinitions } from '@nx/angular/mf';
|
||||
"import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then((remotes: Record<string, string>) => Object.entries(remotes).map(([name, entry]) => ({ name,entry})))
|
||||
.then(remotes => init({name: 'app1', remotes}))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));"
|
||||
`;
|
||||
|
||||
exports[`Init MF --federationType=dynamic should create a host with the correct configurations when --typescriptConfiguration=true 1`] = `
|
||||
"import { setRemoteDefinitions } from '@nx/angular/mf';
|
||||
"import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then((remotes: Record<string, string>) => Object.entries(remotes).map(([name, entry]) => ({ name,entry})))
|
||||
.then(remotes => init({name: 'app1', remotes}))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));"
|
||||
`;
|
||||
|
||||
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly 1`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { Route } from '@angular/router';
|
||||
import { loadRemoteModule } from '@nx/angular/mf';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'remote1',
|
||||
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => loadRemote<typeof import('remote1/Module')>('remote1/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@ -38,12 +40,12 @@ export const appRoutes: Route[] = [
|
||||
exports[`Init MF --federationType=dynamic should wire up existing remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { Route } from '@angular/router';
|
||||
import { loadRemoteModule } from '@nx/angular/mf';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'remote1',
|
||||
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => loadRemote<typeof import('remote1/Module')>('remote1/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@ -59,11 +61,11 @@ import { Route } from '@angular/router';
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'remote2',
|
||||
loadChildren: () => import('remote2/Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => import('remote2/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: 'remote1',
|
||||
loadChildren: () => import('remote1/Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => import('remote1/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@ -175,12 +177,12 @@ export default config;
|
||||
exports[`Init MF should add a remote to dynamic host correctly 1`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { Route } from '@angular/router';
|
||||
import { loadRemoteModule } from '@nx/angular/mf';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'remote1',
|
||||
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => loadRemote<typeof import('remote1/Module')>('remote1/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
@ -192,12 +194,12 @@ export const appRoutes: Route[] = [
|
||||
exports[`Init MF should add a remote to dynamic host correctly when --typescriptConfiguration=true 1`] = `
|
||||
"import { NxWelcomeComponent } from './nx-welcome.component';
|
||||
import { Route } from '@angular/router';
|
||||
import { loadRemoteModule } from '@nx/angular/mf';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: 'remote1',
|
||||
loadChildren: () => loadRemoteModule('remote1', './Module').then(m => m.RemoteEntryModule)
|
||||
loadChildren: () => loadRemote<typeof import('remote1/Module')>('remote1/Module').then(m => m!.RemoteEntryModule)
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
|
||||
@ -47,7 +47,12 @@ export function addRemoteToHost(tree: Tree, options: AddRemoteOptions) {
|
||||
isHostUsingTypescriptConfig
|
||||
);
|
||||
} else if (hostFederationType === 'dynamic') {
|
||||
addRemoteToDynamicHost(tree, options, pathToMFManifest);
|
||||
addRemoteToDynamicHost(
|
||||
tree,
|
||||
options,
|
||||
pathToMFManifest,
|
||||
hostProject.sourceRoot
|
||||
);
|
||||
}
|
||||
|
||||
addLazyLoadedRouteToHostAppModule(tree, options, hostFederationType);
|
||||
@ -114,17 +119,23 @@ function addRemoteToStaticHost(
|
||||
function addRemoteToDynamicHost(
|
||||
tree: Tree,
|
||||
options: AddRemoteOptions,
|
||||
pathToMfManifest: string
|
||||
pathToMfManifest: string,
|
||||
hostSourceRoot: string
|
||||
) {
|
||||
// TODO(Colum): Remove for Nx 22
|
||||
const usingLegacyDynamicFederation = tree
|
||||
.read(`${hostSourceRoot}/main.ts`, 'utf-8')
|
||||
.includes('setRemoteDefinitions(');
|
||||
updateJson(tree, pathToMfManifest, (manifest) => {
|
||||
return {
|
||||
...manifest,
|
||||
[options.appName]: `http://localhost:${options.port}`,
|
||||
[options.appName]: `http://localhost:${options.port}${
|
||||
usingLegacyDynamicFederation ? '' : '/mf-manifest.json'
|
||||
}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(colum): future work: allow dev to pass to path to routing module
|
||||
function addLazyLoadedRouteToHostAppModule(
|
||||
tree: Tree,
|
||||
options: AddRemoteOptions,
|
||||
@ -150,13 +161,22 @@ function addLazyLoadedRouteToHostAppModule(
|
||||
true
|
||||
);
|
||||
|
||||
// TODO(Colum): Remove for Nx 22
|
||||
const usingLegacyDynamicFederation =
|
||||
hostFederationType === 'dynamic' &&
|
||||
tree
|
||||
.read(`${hostAppConfig.sourceRoot}/main.ts`, 'utf-8')
|
||||
.includes('setRemoteDefinitions(');
|
||||
|
||||
if (hostFederationType === 'dynamic') {
|
||||
sourceFile = insertImport(
|
||||
tree,
|
||||
sourceFile,
|
||||
pathToHostRootRouting,
|
||||
'loadRemoteModule',
|
||||
'@nx/angular/mf'
|
||||
usingLegacyDynamicFederation ? 'loadRemoteModule' : 'loadRemote',
|
||||
usingLegacyDynamicFederation
|
||||
? '@nx/angular/mf'
|
||||
: '@module-federation/enhanced/runtime'
|
||||
);
|
||||
}
|
||||
|
||||
@ -164,20 +184,26 @@ function addLazyLoadedRouteToHostAppModule(
|
||||
const exportedRemote = options.standalone
|
||||
? 'remoteRoutes'
|
||||
: 'RemoteEntryModule';
|
||||
const remoteModulePath = `${options.appName.replace(
|
||||
/-/g,
|
||||
'_'
|
||||
)}/${routePathName}`;
|
||||
const routeToAdd =
|
||||
hostFederationType === 'dynamic'
|
||||
? usingLegacyDynamicFederation
|
||||
? `loadRemoteModule('${options.appName.replace(
|
||||
/-/g,
|
||||
'_'
|
||||
)}', './${routePathName}')`
|
||||
: `import('${options.appName.replace(/-/g, '_')}/${routePathName}')`;
|
||||
: `loadRemote<typeof import('${remoteModulePath}')>('${remoteModulePath}')`
|
||||
: `import('${remoteModulePath}')`;
|
||||
|
||||
addRoute(
|
||||
tree,
|
||||
pathToHostRootRouting,
|
||||
`{
|
||||
path: '${options.appName}',
|
||||
loadChildren: () => ${routeToAdd}.then(m => m.${exportedRemote})
|
||||
loadChildren: () => ${routeToAdd}.then(m => m!.${exportedRemote})
|
||||
}`
|
||||
);
|
||||
|
||||
|
||||
@ -23,11 +23,12 @@ export function fixBootstrap(tree: Tree, appRoot: string, options: Schema) {
|
||||
manifestPath = '/module-federation.manifest.json';
|
||||
}
|
||||
|
||||
const fetchMFManifestCode = `import { setRemoteDefinitions } from '@nx/angular/mf';
|
||||
const fetchMFManifestCode = `import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('${manifestPath}')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then((remotes: Record<string, string>) => Object.entries(remotes).map(([name, entry]) => ({ name,entry})))
|
||||
.then(remotes => init({name: '${options.appName}', remotes}))
|
||||
.then(() => ${bootstrapImportCode});`;
|
||||
|
||||
tree.write(mainFilePath, fetchMFManifestCode);
|
||||
|
||||
@ -574,7 +574,7 @@ describe('Init MF', () => {
|
||||
expect(
|
||||
readJson(tree, 'app1/public/module-federation.manifest.json')
|
||||
).toEqual({
|
||||
remote1: 'http://localhost:4201',
|
||||
remote1: 'http://localhost:4201/mf-manifest.json',
|
||||
});
|
||||
expect(
|
||||
tree.read('app1/src/app/app.routes.ts', 'utf-8')
|
||||
@ -609,7 +609,7 @@ describe('Init MF', () => {
|
||||
expect(
|
||||
readJson(tree, 'app1/public/module-federation.manifest.json')
|
||||
).toEqual({
|
||||
remote1: 'http://localhost:4201',
|
||||
remote1: 'http://localhost:4201/mf-manifest.json',
|
||||
});
|
||||
expect(
|
||||
tree.read('app1/src/app/app.routes.ts', 'utf-8')
|
||||
@ -648,7 +648,7 @@ describe('Init MF', () => {
|
||||
expect(
|
||||
readJson(tree, 'app1/public/module-federation.manifest.json')
|
||||
).toEqual({
|
||||
remote1: 'http://localhost:4201',
|
||||
remote1: 'http://localhost:4201/mf-manifest.json',
|
||||
});
|
||||
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
@ -684,7 +684,7 @@ describe('Init MF', () => {
|
||||
expect(
|
||||
readJson(tree, 'app1/public/module-federation.manifest.json')
|
||||
).toEqual({
|
||||
remote1: 'http://localhost:4201',
|
||||
remote1: 'http://localhost:4201/mf-manifest.json',
|
||||
});
|
||||
expect(tree.read('app1/src/app/app.routes.ts', 'utf-8')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@ -45,11 +45,12 @@ export async function setupMf(tree: Tree, rawOptions: Schema) {
|
||||
if (!options.skipPackageJson) {
|
||||
installTask = addDependenciesToPackageJson(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'@module-federation/enhanced': moduleFederationEnhancedVersion,
|
||||
},
|
||||
{
|
||||
'@nx/web': nxVersion,
|
||||
'@nx/webpack': nxVersion,
|
||||
'@module-federation/enhanced': moduleFederationEnhancedVersion,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -21,20 +21,84 @@ const remoteModuleMap = new Map<string, unknown>();
|
||||
const remoteContainerMap = new Map<string, unknown>();
|
||||
let initialSharingScopeCreated = false;
|
||||
|
||||
/**
|
||||
* @deprecated Use Runtime Helpers from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
*/
|
||||
export function setRemoteUrlResolver(
|
||||
_resolveRemoteUrl: ResolveRemoteUrlFunction
|
||||
) {
|
||||
resolveRemoteUrl = _resolveRemoteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use init() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you have a remote app called `my-remote-app` and you want to use the `http://localhost:4201/mf-manifest.json` as the remote url, you should change it from:
|
||||
* ```ts
|
||||
* import { setRemoteDefinitions } from '@nx/react/mf';
|
||||
*
|
||||
* setRemoteDefinitions({
|
||||
* 'my-remote-app': 'http://localhost:4201/mf-manifest.json'
|
||||
* });
|
||||
* ```
|
||||
* to use init():
|
||||
* ```ts
|
||||
* import { init } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* init({
|
||||
* name: 'host',
|
||||
* remotes: [{
|
||||
* name: 'my-remote-app',
|
||||
* entry: 'http://localhost:4201/mf-manifest.json'
|
||||
* }]
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function setRemoteDefinitions(definitions: Record<string, string>) {
|
||||
remoteUrlDefinitions = definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use registerRemotes() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you set a remote app with `setRemoteDefinition` such as:
|
||||
* ```ts
|
||||
* import { setRemoteDefinition } from '@nx/react/mf';
|
||||
*
|
||||
* setRemoteDefinition(
|
||||
* 'my-remote-app',
|
||||
* 'http://localhost:4201/mf-manifest.json'
|
||||
* );
|
||||
* ```
|
||||
* change it to use registerRemotes():
|
||||
* ```ts
|
||||
* import { registerRemotes } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* registerRemotes([
|
||||
* {
|
||||
* name: 'my-remote-app',
|
||||
* entry: 'http://localhost:4201/mf-manifest.json'
|
||||
* }
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
export function setRemoteDefinition(remoteName: string, remoteUrl: string) {
|
||||
remoteUrlDefinitions[remoteName] = remoteUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use loadRemote() from '@module-federation/enhanced/runtime' instead. This will be removed in Nx 22.
|
||||
* If you set a load a remote with `loadRemoteModule` such as:
|
||||
* ```ts
|
||||
* import { loadRemoteModule } from '@nx/react/mf';
|
||||
*
|
||||
* loadRemoteModule('my-remote-app', './Module').then(m => m.RemoteEntryModule);
|
||||
* ```
|
||||
* change it to use loadRemote():
|
||||
* ```ts
|
||||
* import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
*
|
||||
* loadRemote<typeof import('my-remote-app/Module')>('my-remote-app/Module').then(m => m.RemoteEntryModule);
|
||||
* ```
|
||||
*/
|
||||
export async function loadRemoteModule(remoteName: string, moduleName: string) {
|
||||
const remoteModuleKey = `${remoteName}:${moduleName}`;
|
||||
if (remoteModuleMap.has(remoteModuleKey)) {
|
||||
|
||||
@ -0,0 +1,177 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederationForSSR } = require('@nx/rspack/module-federation');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(
|
||||
withNx(),
|
||||
withReact({ ssr: true }),
|
||||
withModuleFederationForSSR(defaultConfig, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR 2`] = `
|
||||
"// @ts-check
|
||||
|
||||
/**
|
||||
* @type {import('@nx/rspack/module-federation').ModuleFederationConfig}
|
||||
**/
|
||||
const moduleFederationConfig = {
|
||||
name: 'test',
|
||||
remotes: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Nx requires a default export of the config to allow correct resolution of the module federation graph.
|
||||
**/
|
||||
module.exports = moduleFederationConfig;
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR when --typescriptConfiguration=true 1`] = `
|
||||
"import { composePlugins, withNx, withReact } from '@nx/rspack';
|
||||
import { withModuleFederationForSSR } from '@nx/rspack/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
const defaultConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(
|
||||
withNx(),
|
||||
withReact({ ssr: true }),
|
||||
withModuleFederationForSSR(defaultConfig, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs for SSR when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
remotes: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Nx requires a default export of the config to allow correct resolution of the module federation graph.
|
||||
**/
|
||||
export default config;
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=false 1`] = `
|
||||
"const { composePlugins, withNx, withReact } = require('@nx/rspack');
|
||||
const { withModuleFederation } = require('@nx/rspack/module-federation');
|
||||
|
||||
const baseConfig = require('./module-federation.config');
|
||||
|
||||
const config = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
module.exports = composePlugins(
|
||||
withNx(),
|
||||
withReact(),
|
||||
withModuleFederation(config, { dts: false })
|
||||
);
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=false 2`] = `
|
||||
"/**
|
||||
* Nx requires a default export of the config to allow correct resolution of the module federation graph.
|
||||
**/
|
||||
module.exports = {
|
||||
name: 'test',
|
||||
/**
|
||||
* To use a remote that does not exist in your current Nx Workspace
|
||||
* You can use the tuple-syntax to define your remote
|
||||
*
|
||||
* remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']]
|
||||
*
|
||||
* You _may_ need to add a \`remotes.d.ts\` file to your \`src/\` folder declaring the external remote for tsc, with the
|
||||
* following content:
|
||||
*
|
||||
* declare module 'my-external-remote';
|
||||
*
|
||||
*/
|
||||
remotes: [],
|
||||
};
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=true 1`] = `
|
||||
"import {composePlugins, withNx, withReact} from '@nx/rspack';
|
||||
import {withModuleFederation, ModuleFederationConfig} from '@nx/rspack/module-federation';
|
||||
|
||||
import baseConfig from './module-federation.config';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
...baseConfig,
|
||||
};
|
||||
|
||||
// Nx plugins for rspack to build config object from Nx options and context.
|
||||
/**
|
||||
* DTS Plugin is disabled in Nx Workspaces as Nx already provides Typing support for Module Federation
|
||||
* The DTS Plugin can be enabled by setting dts: true
|
||||
* Learn more about the DTS Plugin here: https://module-federation.io/configure/dts.html
|
||||
*/
|
||||
export default composePlugins(withNx(), withReact(), withModuleFederation(config, { dts: false }));
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`hostGenerator bundler=rspack should generate host files and configs when --typescriptConfiguration=true 2`] = `
|
||||
"import { ModuleFederationConfig } from '@nx/rspack/module-federation';
|
||||
|
||||
const config: ModuleFederationConfig = {
|
||||
name: 'test',
|
||||
/**
|
||||
* To use a remote that does not exist in your current Nx Workspace
|
||||
* You can use the tuple-syntax to define your remote
|
||||
*
|
||||
* remotes: [['my-external-remote', 'https://nx-angular-remote.netlify.app']]
|
||||
*
|
||||
* You _may_ need to add a \`remotes.d.ts\` file to your \`src/\` folder declaring the external remote for tsc, with the
|
||||
* following content:
|
||||
*
|
||||
* declare module 'my-external-remote';
|
||||
*
|
||||
*/
|
||||
remotes: [
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Nx requires a default export of the config to allow correct resolution of the module federation graph.
|
||||
**/
|
||||
export default config;
|
||||
"
|
||||
`;
|
||||
@ -4,16 +4,16 @@ import NxWelcome from "./nx-welcome";
|
||||
<%_ } _%>
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
<%_ if (dynamic) { _%>
|
||||
import { loadRemoteModule } from '@nx/react/mf';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
<%_ } _%>
|
||||
|
||||
<%_ if (remotes.length > 0) {
|
||||
remotes.forEach(function(r) {
|
||||
if (dynamic) { _%>
|
||||
const <%= r.className %> = React.lazy(() => loadRemoteModule('<%= r.fileName %>', './Module'))
|
||||
<%_ } else { _%>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ } _%>
|
||||
remotes.forEach(function(r) { _%>
|
||||
<%_ if (dynamic) { _%>
|
||||
const <%= r.className %> = React.lazy(() => loadRemote('<%= r.fileName %>/Module') as any)
|
||||
<%_ } else { _%>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ } _%>
|
||||
<%_ }); _%>
|
||||
<%_ } _%>
|
||||
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<%_ if (dynamic) { _%>
|
||||
import { setRemoteDefinitions } from '@nx/react/mf';
|
||||
import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then((remotes: Record<string, string>) =>
|
||||
Object.entries(remotes).map(([name, entry]) => ({ name, entry }))
|
||||
)
|
||||
.then((remotes) => init({ name: '<%= projectName %>', remotes }))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
<%_ } else { _%>
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
<%_ } _%>
|
||||
@ -3,12 +3,19 @@ import * as React from 'react';
|
||||
import NxWelcome from "./nx-welcome";
|
||||
<%_ } _%>
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
<%_ if (dynamic) { _%>
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
<%_ } _%>
|
||||
|
||||
<%_ if (remotes.length > 0) {
|
||||
remotes.forEach(function(r) { _%>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ }); _%>
|
||||
<% } %>
|
||||
<%_ if (dynamic) { _%>
|
||||
const <%= r.className %> = React.lazy(() => loadRemote('<%= r.fileName %>/Module') as any)
|
||||
<%_ } else { _%>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ } _%>
|
||||
<%_ }); _%>
|
||||
<%_ } _%>
|
||||
export function App() {
|
||||
return (
|
||||
<React.Suspense fallback={null}>
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<%_ if (dynamic) { _%>
|
||||
import { setRemoteDefinitions } from '@nx/react/mf';
|
||||
import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then((remotes: Record<string, string>) =>
|
||||
Object.entries(remotes).map(([name, entry]) => ({ name, entry }))
|
||||
)
|
||||
.then((remotes) => init({ name: '<%= projectName %>', remotes }))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
<%_ } else { _%>
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
<%_ } _%>
|
||||
@ -4,11 +4,20 @@ import NxWelcome from "./nx-welcome";
|
||||
<%_ } _%>
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
|
||||
<% if (remotes.length > 0) {
|
||||
remotes.forEach(function(r) { %>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ if (dynamic) { _%>
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
<%_ } _%>
|
||||
|
||||
<%_ if (remotes.length > 0) {
|
||||
remotes.forEach(function(r) { _%>
|
||||
<%_ if (dynamic) { _%>
|
||||
const <%= r.className %> = React.lazy(() => loadRemote('<%= r.fileName %>/Module') as any)
|
||||
<%_ } else { _%>
|
||||
const <%= r.className %> = React.lazy(() => import('<%= r.fileName %>/Module'));
|
||||
<%_ } _%>
|
||||
<%_ }); _%>
|
||||
<%_ } _%>
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<React.Suspense fallback={null}>
|
||||
|
||||
@ -1,10 +1,13 @@
|
||||
<%_ if (dynamic) { _%>
|
||||
import { setRemoteDefinitions } from '@nx/react/mf';
|
||||
import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then(definitions => setRemoteDefinitions(definitions))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then((remotes: Record<string, string>) =>
|
||||
Object.entries(remotes).map(([name, entry]) => ({ name, entry }))
|
||||
)
|
||||
.then((remotes) => init({ name: '<%= projectName %>', remotes }))
|
||||
.then(() => import('./bootstrap').catch(err => console.error(err)));
|
||||
<%_ } else { _%>
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
import('./bootstrap').catch(err => console.error(err));
|
||||
<%_ } _%>
|
||||
@ -85,8 +85,7 @@ jest.mock('@nx/devkit', () => {
|
||||
};
|
||||
});
|
||||
|
||||
// TODO(colum): turn these on when rspack is moved into the main repo
|
||||
xdescribe('hostGenerator', () => {
|
||||
describe('hostGenerator', () => {
|
||||
let tree: Tree;
|
||||
|
||||
// TODO(@jaysoo): Turn this back to adding the plugin
|
||||
@ -121,9 +120,9 @@ xdescribe('hostGenerator', () => {
|
||||
|
||||
expect(tree.exists('test/tsconfig.json')).toBeTruthy();
|
||||
|
||||
expect(tree.exists('test/src/bootstrap.js')).toBeTruthy();
|
||||
expect(tree.exists('test/src/main.js')).toBeTruthy();
|
||||
expect(tree.exists('test/src/app/app.js')).toBeTruthy();
|
||||
expect(tree.exists('test/src/bootstrap.jsx')).toBeTruthy();
|
||||
expect(tree.exists('test/src/main.jsx')).toBeTruthy();
|
||||
expect(tree.exists('test/src/app/app.jsx')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should generate host files and configs when --js=false', async () => {
|
||||
@ -206,6 +205,7 @@ xdescribe('hostGenerator', () => {
|
||||
});
|
||||
|
||||
const packageJson = readJson(tree, 'package.json');
|
||||
console.log(packageJson);
|
||||
expect(packageJson.devDependencies['@nx/web']).toBeDefined();
|
||||
});
|
||||
|
||||
@ -363,5 +363,67 @@ xdescribe('hostGenerator', () => {
|
||||
})
|
||||
).rejects.toThrowError(`Invalid remote name provided: ${remote}.`);
|
||||
});
|
||||
|
||||
it('should generate create files with dynamic host', async () => {
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
const remote = 'remote1';
|
||||
|
||||
await hostGenerator(tree, {
|
||||
directory: 'myhostapp',
|
||||
remotes: [remote],
|
||||
dynamic: true,
|
||||
e2eTestRunner: 'none',
|
||||
linter: Linter.None,
|
||||
style: 'css',
|
||||
unitTestRunner: 'none',
|
||||
typescriptConfiguration: false,
|
||||
bundler: 'rspack',
|
||||
});
|
||||
|
||||
expect(tree.read('myhostapp/src/main.ts', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import { init } from '@module-federation/enhanced/runtime';
|
||||
|
||||
fetch('/assets/module-federation.manifest.json')
|
||||
.then((res) => res.json())
|
||||
.then((remotes: Record<string, string>) =>
|
||||
Object.entries(remotes).map(([name, entry]) => ({ name, entry }))
|
||||
)
|
||||
.then((remotes) => init({ name: 'myhostapp', remotes }))
|
||||
.then(() => import('./bootstrap').catch((err) => console.error(err)));
|
||||
"
|
||||
`);
|
||||
expect(tree.read('myhostapp/src/app/app.tsx', 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import * as React from 'react';
|
||||
import NxWelcome from './nx-welcome';
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
import { loadRemote } from '@module-federation/enhanced/runtime';
|
||||
|
||||
const Remote1 = React.lazy(() => loadRemote('remote1/Module') as any);
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<React.Suspense fallback={null}>
|
||||
<ul>
|
||||
<li>
|
||||
<Link to="/">Home</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/remote1">Remote1</Link>
|
||||
</li>
|
||||
</ul>
|
||||
<Routes>
|
||||
<Route path="/" element={<NxWelcome title="myhostapp" />} />
|
||||
<Route path="/remote1" element={<Remote1 />} />
|
||||
</Routes>
|
||||
</React.Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -23,7 +23,10 @@ import { updateModuleFederationE2eProject } from './lib/update-module-federation
|
||||
import { NormalizedSchema, Schema } from './schema';
|
||||
import { addMfEnvToTargetDefaultInputs } from '../../utils/add-mf-env-to-inputs';
|
||||
import { isValidVariable } from '@nx/js';
|
||||
import { moduleFederationEnhancedVersion } from '../../utils/versions';
|
||||
import {
|
||||
moduleFederationEnhancedVersion,
|
||||
nxVersion,
|
||||
} from '../../utils/versions';
|
||||
import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
|
||||
|
||||
@ -147,8 +150,10 @@ export async function hostGenerator(
|
||||
|
||||
const installTask = addDependenciesToPackageJson(
|
||||
host,
|
||||
{},
|
||||
{ '@module-federation/enhanced': moduleFederationEnhancedVersion }
|
||||
{ '@module-federation/enhanced': moduleFederationEnhancedVersion },
|
||||
{
|
||||
'@nx/web': nxVersion,
|
||||
}
|
||||
);
|
||||
tasks.push(installTask);
|
||||
|
||||
|
||||
@ -117,7 +117,10 @@ export function addModuleFederationFiles(
|
||||
pathToMFManifest,
|
||||
`{
|
||||
${defaultRemoteManifest
|
||||
.map(({ name, port }) => `"${name}": "http://localhost:${port}"`)
|
||||
.map(
|
||||
({ name, port }) =>
|
||||
`"${name}": "http://localhost:${port}/mf-manifest.json"`
|
||||
)
|
||||
.join(',\n')}
|
||||
}`
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user