What are Micro Frontends?
Micro frontends extend the concept of microservices to the frontend world, coined first by Thoughtworks. In traditional frontend development, a single application might handle everything from user interfaces to business logic. Micro frontends break this monolithic approach into smaller, more manageable pieces.
Here’s a high-level overview of micro frontends:
Modularity: Just as microservices break down backend services into smaller, independent pieces, micro frontends decompose the frontend into smaller, self-contained applications or components. Each of these pieces can be developed, tested, and deployed independently.
Ownership: Different teams can own different micro frontends. For instance, one team might handle the user profile section, while another team focuses on the shopping cart. This can help with scaling teams and allows for specialized focus.
Technology Agnostic: Micro frontends can use different technologies or frameworks. One micro frontend could be built with React, while another might use Vue.js or Angular. This flexibility can be beneficial for integrating new technologies gradually.
Integration: The individual micro frontends are integrated into a single application. This can be done through various methods, such as embedding iframes, using JavaScript-based integration techniques, or through build-time composition.
Deployment: Micro frontends can be deployed independently. This means updates or changes in one micro frontend don’t necessarily affect others, which can reduce deployment risk and time.
User Experience: Despite being developed and deployed separately, micro frontends aim to provide a seamless user experience. The integration points should be designed to ensure that the application feels cohesive to users.
Module Federation
Code split into smaller deployable modules
Can be shared and consumed at runtime between applications
Concepts: Host, Remote and Federate Module
Host is an application that consumes federated modules from remote applications at runtimeRemote is an application that exposes a federated module that can be fetched over the network at runtime
Federated Module is any valid JavaScript module that is exposed by a remote application with the aim that it will be consumed by a host application
Native Federation
Native Federation is a browser-native implementation that can be used independently of build tools and frameworks
Avoids the complexity and overhead of configuring tools like Webpack.
Facilitates the direct use of native JavaScript functionalities for module imports.
May reduce build time overhead but heavily depends on browser support.
Step-wise setup
Create a Shell/Host app and N Micro frontend apps based on your need.
Our setup would be @angular-architects/native-federation library.
Create angular apps
ng new shell --routing --style=scss ng new mfe1 --routing --style=scss ng new mfe2 --routing --style=scss
Add native federation library to each project.
cd shell ng add @angular-architects/native-federation cd mfe1 ng add @angular-architects/native-federation cd mfe2 ng add @angular-architects/native-federation
Configure settings for Native federation: Shell
The above commands generate
federation.config.js
file with content like below inside each project. Removeexposes
property from shell as we do not expose anything from the Shell app.const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config'); module.exports = withNativeFederation({ name: 'shell', exposes: ..... // remove shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }), }, skip: [ 'rxjs/ajax', 'rxjs/fetch', 'rxjs/testing', 'rxjs/webSocket', ] });
Create the
assets
folder under/src
For each MFE,
Configure
federation.config.json
to expose component/routes if needed.Modify
angular.json
to change port where we serve the app.Our shell runs on port
4200
by default. The MFEs need to run on separate ports."architect": { "serve-original": { "options": { "port": <change-port-here> } }, }
Under Shell, create a new file
federation.manifest.json
file inside theassets
folder with following content{ "mfe1": "http://localhost:4201/remoteEntry.json", "mfe2": "http://localhost:4202/remoteEntry.json" }
Modify
angular.json
to add assets path underesbuild
options."esbuild": { "options": { "assets": [ { "glob": "**/*", "input": "public" }, "src/assets" ], }, }
Add module federation initialization inside
main.ts
. This is the critical part where we dynamically load MFEs before we bootstrap our Shell app.import { initFederation } from '@angular-architects/native-federation'; initFederation('/assets/federation.manifest.json') .catch((err) => console.error(err)) .then((_) => import('./bootstrap')) .catch((err) => console.error(err));
Configure routes in Angular
export const routes: Routes = [ { path: 'mfe1', loadComponent: () => loadRemoteModule('mfe1', './Component').then((m) => m.AppComponent), }, { path: 'mfe2', loadComponent: () => loadRemoteModule('mfe2', './Component').then((m) => m.AppComponent), }, ];
Run MFEs
cd mfe1 npm start
Run shell
cd shell npm start
Viola!
This is how our shell looks
This is how the MFE is hosted
Though the setup looks easy, the advanced configurations get tougher :).
MFE provides with many benefits
Faster deployment and Faster releases = reduced costs
Multiple teams with different responsibilities
Technology freedom
Easy scaling
Continuous deployment
MFE also has some challenges which cannot be overlooked
Design consistency
More repos, more pipelines
Micro frontend Anarchy - Details in next blog :)
That’s all folks!