In the rapidly evolving landscape of web development, the concept of micro frontends has emerged as a powerful approach to building scalable and maintainable web applications. This architecture allows teams to develop and deploy independent parts of a web application, which can be integrated into a larger application at runtime. A key technology enabling this architecture is Module Federation, introduced in Webpack 5, which allows JavaScript applications to dynamically load code from other applications at runtime.
This blog post delves into setting up a shell application and a remote micro frontend using Angular, Web Components, and Module Federation based on @angular-architects/module-federation.
The source code of a demo implementation is provided at GitLab.
Setting Up the Module Federation
Create a New Angular Application
Start by creating a new Angular application. Use the Angular CLI to generate a new project with or without a standalone component configuration.ng new --no-standalone mfe-1
Add Module Federation Plugin
Use the Angular Architects’ Module Federation plugin to configure your application as a remote one.ng add @angular-architects/module-federation --type remote --port 4201
Option 1: Expose an Angular Module as a Micro Frontend
Create a new Angular Module ng g module mfe2-test --routing
and include your Micro Frontend Components ng g component welcome
.
In the webpack.config.js
, expose the micro frontend’s entry point, and define shared dependencies.
This configuration allows the micro frontend to be dynamically loaded by the shell application.
const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'mfe-2',
filename: "remoteEntry.js",
exposes: {
'./Mfe2TestModule': './src/app/mfe2-test/mfe2-test.module.ts',
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
Option 2: Expose an Angular Application as a Web Component
Building an entire Angular Application as a Web Component and publishing it using Module Federation involves several steps. This process allows the encapsulation of an Angular application into a single reusable Web Component, which can then be dynamically loaded into other applications using Module Federation.
Add Angular Elements
Angular Elements is a package that allows Angular components to be packaged as custom elements, which can be used in any web application. ng add @angular/elements
Adjust AppModule for Web Component Creation
Modify the AppModule
to use the createCustomElement
function from @angular/elements
to create a Web Component. This is done by removing the bootstrap: [AppComponent]
line and using createCustomElement
in the ngDoBootstrap
method. The approach allows the application to be bootstrapped as a Web Component. For switching between bootstrapping as a Web Component and bootstrapping with Angular optional logic like below can be built.
import {ApplicationRef, DoBootstrap, Injector, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {environment} from '../environments/environment';
import {createCustomElement} from '@angular/elements';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
})
export class AppModule implements DoBootstrap {
constructor(private injector: Injector) {
}
ngDoBootstrap(app: ApplicationRef) {
if (environment.production) {
// bootstrap as webcomponent for production
const ce = createCustomElement(AppComponent, {injector: this.injector});
customElements.define('mfe1-root', ce);
} else {
// bootstrap with angular. for local development
app.bootstrap(AppComponent);
}
}
}
Change the index.html
Tag
The tag in index.html
is changed to the defined element tag in AppModule
. This step is crucial to ensure the custom element is correctly recognized and rendered in the host application.
<!-- bootstrap with angular-->
<app-root></app-root>
<!-- bootstrap webcomponent-->
<mfe1-root></mfe1-root>
Expose the Web Component using Module Federation
In the webpack.config.js
, expose the micro frontend’s Web Component entry point, and define shared dependencies.
This configuration allows the micro frontend to be dynamically loaded by the shell application.
const {withModuleFederationPlugin} = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'mfe-1',
filename: "remoteEntry.js",
exposes: {
'./Mfe1AngularElement': './src/bootstrap.ts',
},
shared: {
"@angular/core": {requiredVersion: "auto"},
"@angular/common": {requiredVersion: "auto"},
"@angular/router": {requiredVersion: "auto"},
"rxjs": {requiredVersion: "auto"},
}
});
Integrating the Micro Frontend into the Shell Application
Add Module Federation Plugin
Use the Angular Architects’ Module Federation plugin to configure your application as a host and check the updated and created files.
This setup allows the shell application to load the micro frontend dynamically.ng add @angular-architects/module-federation --type host --port 4200
Load Micro Frontend using a Router
For loading the micro frontend dynamically into the shell application while accessing a specific route, use Angular’s router.
import { loadRemoteModule } from '@angular-architects/module-federation';
import {startsWith, WebComponentWrapper, WebComponentWrapperOptions} from '@angular-architects/module-federation-tools';
...
// Option 1: Load exposed Angular Module as a Micro Frontend
{
path: 'mfe2',
loadChildren: () =>
loadRemoteModule({
type: 'module',
remoteEntry: 'http://localhost:4202/remoteEntry.js',
exposedModule: './Mfe2TestModule'
})
.then(m => m.Mfe2TestModule)
},
// Option 2: Load exposed Angular Application as a Web Component
{
// used because microfrontend has own router -> mfe needs to have same path
matcher: startsWith('mfe1'),
component: WebComponentWrapper,
data: {
type: 'module',
remoteEntry: 'http://localhost:4201/remoteEntry.js',
exposedModule: './Mfe1AngularElement',
elementName: 'mfe1-root'
} as WebComponentWrapperOptions
},
Load Several Angular Applications Together in One Browser Window
Note: Only needed for Option 2, where an Angular Micro Frontend is loaded as a second Angular Application into the Shell!
To enable multiple Angular applications to work together within a single browser window, leveraging Module Federation, requires a specific approach. The @angular-architects/module-federation-tools
package provides a lightweight solution to facilitate this integration.
First, install @angular-architects/module-federation-tools
in both, your micro frontend and the shell application. This can be done using: npm i @angular-architects/module-federation-tools -D
This package is designed to simplify the process of setting up Module Federation in Angular applications, making it easier to manage dependencies and share code between different parts of your application.
Bootstrap with Module Federation Tools
Replace Angular’s default bootstrap method with the one provided by “@angular-architects/module-federation-tools
“, for both applications, shell, and micro frontend! The bootstrap
function from the package is used to initialize the application, with options specifying whether the application is a micro frontend or a shell.
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { bootstrap } from '@angular-architects/module-federation-tools';
bootstrap(AppModule, {
production: environment.production,
appType: 'microfrontend' // for micro frontend
// appType: 'shell', // for shell
});
Shared Dependencies and Module Federation Configuration
The shared
section of the Module Federation configuration allows you to define libraries shared between your federated modules. This is crucial for preventing the same library from being loaded multiple times, optimizing the loading process, and ensuring that shared dependencies are correctly managed. For instance, if your shell application and micro frontends use Angular, you can define Angular libraries as singletons in the shared
section to ensure a single download.
Further readings on Module Federation and dependency management with different versions:
https://www.angulararchitects.io/blog/multi-framework-and-version-micro-frontends-with-module-federation-your-4-steps-guide/
https://medium.com/@marvusm.mmi/webpack-module-federation-think-twice-before-sharing-a-dependency-18b3b0e352cb
https://www.angulararchitects.io/blog/the-microfrontend-revolution-part-2-module-federation-with-angular/