Microfrontends
The Goal
Microfrontends (MFEs) make it possible for large teams to indepently develop and deploy features in isolation.
We will discuss how MFEs work with module federation and the difference between static and dynamic federation.
Our goal is to wire up our host application to load in our remote features as MFEs.
The Commands
Create a dashboard host application and remote applications called users, challenges, flashcards, and notes.
npx nx g @nx/angular:host dashboard \
--style=scss \
--dynamic=true \
--e2eTestRunner=cypress \
--remotes=users,challenges,flashcards,notes \
--projectNameAndRootFormat=derived \
--ssr=false
The Code
import { ModuleFederationConfig } from '@nx/webpack';
const config: ModuleFederationConfig = {
name: 'challenges',
exposes: {
'./Routes': 'apps/challenges/src/app/remote-entry/entry.routes.ts',
},
};
export default config;
import { ModuleFederationConfig } from '@nx/webpack';
const config: ModuleFederationConfig = {
name: 'dashboard',
remotes: [],
};
export default config;
import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/assets/module-federation.manifest.json')
.then((res) => res.json())
.then((definitions) => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap').catch((err) => console.error(err)));
export const appRoutes: Route[] = [
{
path: 'notes',
loadChildren: () =>
loadRemoteModule('notes', './Routes').then((m) => m.remoteRoutes),
},
{
path: 'flashcards',
loadChildren: () =>
loadRemoteModule('flashcards', './Routes').then((m) => m.remoteRoutes),
},
{
path: 'challenges',
loadChildren: () =>
loadRemoteModule('challenges', './Routes').then((m) => m.remoteRoutes),
},
{
path: 'users',
loadChildren: () =>
loadRemoteModule('users', './Routes').then((m) => m.remoteRoutes),
},
{
path: '',
component: HomeComponent,
},
];
Remote
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { RemoteEntryComponent } from './app/remote-entry/entry.component';
bootstrapApplication(RemoteEntryComponent, appConfig).catch((err) =>
console.error(err)
);
import { ModuleFederationConfig } from '@nx/webpack';
const config: ModuleFederationConfig = {
name: 'challenges',
exposes: {
'./Routes': 'apps/challenges/src/app/remote-entry/entry.routes.ts',
},
};
export default config;
Static to Dynamic
// apps/dashboard/src/assets/module-federation.manifest.json
{
"challenges": "http://localhost:4201",
"flashcards": "http://localhost:4202"
}
// apps/dashboard/src/main.ts
import { setRemoteDefinitions } from '@nx/angular/mf';
fetch('/assets/module-federation.manifest.json')
.then((res) => res.json())
.then((definitions) => setRemoteDefinitions(definitions))
.then(() => import('./bootstrap').catch((err) => console.error(err)));
// apps/dashboard/module-federation.config.ts
import { ModuleFederationConfig } from '@nx/webpack';
const config: ModuleFederationConfig = {
name: 'dashboard',
remotes: [],
};
export default config;
// apps/dashboard/src/app/app.routes.ts
import { NxWelcomeComponent } from './nx-welcome.component';
import { Route } from '@angular/router';
import { loadRemoteModule } from '@nx/angular/mf';
export const appRoutes: Route[] = [
{
path: 'flashcards',
loadChildren: () =>
loadRemoteModule('flashcards', './Routes').then((m) => m.remoteRoutes),
},
{
path: 'challenges',
loadChildren: () =>
loadRemoteModule('challenges', './Routes').then((m) => m.remoteRoutes),
},
{
path: '',
component: NxWelcomeComponent,
},
];
Challenges
Generate the