Posted on April 10, 2023
In this article, we will discuss Microfrontend implementation and what is the importance of building scalable web applications. We will be using Webpack Module Federation as a tool to build Microfrontends but there are tools as well i.e. single-spa (I take some other day to talk about this library) This article is written for Angular and Angular CLI 15. Make sure you have a compatible version if you want to try it out. Let's understand by simple product example 'ERP (Enterprise Resource Planning)'. Look at the diagram below. A Monolithic approach contains a single code base for Product, Supply, and Employee, where all teams are working and functionality cross touched by different teams. This means that any changes made to the codebase would affect the entire system and it makes more difficult to scale, test, and maintain A Microfrontend could be created for the Product Module, while another created for Supply Module and Employee Module. Each Microfrontend would be self-contained it's own codebase, UI, and functionality and can be developed and deployed separately. Module Federation is a feature introduced in Webpack 5 that allows for sharing modules across multiple applications or micro-frontends in a decentralized way. Webpack Module Federation solves this problem by enabling applications to share code and communicate with each other at runtime. The basic idea of Module Federation is to define a remote entry point in one application that exports certain modules. These modules can then be consumed by another application as remote modules, without having to bundle them in the consuming application. A sample code of the module federation The source code for this article can be found on GitHub. Create an angular application using The "shell" in micro frontend development is like the outer layer of an application. It's a separate component that's responsible for organizing and coordinating the different parts (or "micro frontends") that make up the whole application. Think of it like the framework that holds everything together. It provides a common platform for all the micro frontends to work together, ensuring a smooth and cohesive user experience. This command will generate the following files. product -> app-routing.module.ts supply -> app-routing.module.ts employees -> app-routing.module.ts To communicate between a shell and a micro frontend following are the options available For Demo purposes, we will be using Event Bus. We will dispatch the event using the window.dispatchEvent() method, which sends the event to all event listeners that are registered for this event to all micro frontends The source code for this article can be found on GitHub.Introduction
Final implementation will look like 👇
Understanding Microfrontends
Microfrontends
is an architectural style where frontend applications can easily be developed, tested, and deployed independently, while still appearing as a single product. This technique is called Microfrontend. In comparison Monolithic
frontend architectures have a single codebase for frontend applications, which means the change in a code base in a single place can affect the entire application, which can make it more difficult to scale, test, and maintain. Webpack Module Federation
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'http://localhost:3001/remoteEntry.js',
},
}),
],
};
Provide a step-by-step guide for building a Microfrontend using Webpack Module Federation.
angular cli
i.e.
erp-shell-mef
erp-products-mef
erp-supply-mef
and erp-employees-mef
@angular-architects/module-federation
schematic to create a shell Microfrontendng add @angular-architects/module-federation --project erp-shell-mef --port 4200 --type host
@angular-architects/module-federation
schematic to create a micro-frontendng add @angular-architects/module-federation --project erp-products-mef --port 4201 --type remote
ng add @angular-architects/module-federation --project erp-supply-mef --port 4202 --type remote
ng add @angular-architects/module-federation --project erp-employees-mef --port 4203 --type remote
productModule
.const routes: Routes = [
{
path: 'product',
loadChildren: () => import('erp-products-mef/productModule').then(m => m.ProductModule)
},
];
const routes: Routes = [
{
path: 'supply', loadChildren: () => import('./supply/supply.module').then(m => m.SupplyModule)
},
];
const routes: Routes = [
{
path: 'employees', loadChildren: () => import('./employee/employee.module').then(m => m.EmployeeModule)
},
];
declare module 'erp-products-mef/ProductModule';
declare module 'erp-supply-mef/SupplyModule';
declare module 'erp-employees-mef/EmployeeModule';
erp-products-mef
are pointing to another micro-frontend. You need to tweak webpack.config.js
and new host 4201
, do this for all micro-frontends and other hosts as well.const { shareAll, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
remotes: {
"erp-products-mef": "http://localhost:4201/remoteEntry.js",
"erp-supply-mef": "http://localhost:4202/remoteEntry.js",
"erp-employees-mef": "http://localhost:4203/remoteEntry.js",
},
shared: {
...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }),
},
});
npm run start
Sharing Packages between micro frontends
const { share, withModuleFederationPlugin } = require('@angular-architects/module-federation/webpack');
module.exports = withModuleFederationPlugin({
name: 'erp-products-mef',
exposes: {
'./ProductModule': './src/app/product/product.module.ts',
},
shared: share({
"@angular/core": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/common/http": { singleton: true, strictVersion: true, requiredVersion: 'auto' },
"@angular/router": { singleton: true, strictVersion: true, requiredVersion: 'auto' }
})
});
Communicate between microfrontend using
CustomEvent
//Shell microfrontend
sendNotification() {
const event = new CustomEvent('product_mef_delete_event', {
detail: {
name: "Product 1",
}
});
window.dispatchEvent(event);
}
//product microfrontend
ngOnInit(): void {
window.addEventListener('product_mef_delete_event', (e: any) => {
console.log(e.detail.name);
var foundProduct = this.products.find(x => x.name === e.detail.name);
const indexToRemove = this.products.indexOf(foundProduct!);
if (indexToRemove !== -1) { // Check if the element exists in the array
this.products.splice(indexToRemove, 1); // Remove one element at the found index
} else {
window.alert("Product 1 not found")
}
});
}
Best Practices for Micro frontend Development
References