Micro Frontends - A Simple Demo
In this article, take a look at micro frontends and see a simple demo.
Join the DZone community and get the full member experience.
Join For FreeThere are many articles in DZone and videos on the internet on Micro Frontends. So I'll skip the details and jump to the article. This article is about demonstrating the development of Micro Frontends using vanilla web components. I consider two Micro Frontends that are deployed independently of each other to two sub-domains and using different code repositories. These frontends are then assembled as one SPA. I'll then discuss the communication between the frontends and then present a working online demo of this SPA.
The SPA - The Desired Result
The screenshot of the SPA for this demo is:
where two micro frontends, productsApp and cartApp, are assembled as one SPA. The user can select one or more products, add them to the cart and then view their selections in the cart. The two frontend apps are deployed independently of each other with their own sub-domains. The code for the two apps are also in separate repositories.
Micro Frontend #1 - Products App
The productsApp.html
uses a web component, ProductsComp with a custom element like:
<products-comp id="productsApp" ></products-comp>
The source for products-comp.js
is:
x
class ProductsComp extends HTMLElement {
constructor() {
super();
this.addEventListener('change', e => {
if (e.target.type === "checkbox") {
let atleastOneChecked = false;
for (let el of this.getElementsByTagName('input')) {
if (el.checked) {
atleastOneChecked = true;
break;
}
}
}
});
this.addEventListener('click', e => {
if (e.target.type === "submit") {
let selectedFruits = new Array();
for (let el of this.getElementsByTagName('input')) {
if (el.checked) {
selectedFruits.push(el.getAttribute("value"));
}
};
if (selectedFruits.length >= 0) {
this.dispatchEvent(
new CustomEvent('selectedFruits', {
detail: selectedFruits
})
);
}
}
});
}
connectedCallback() {
this.innerHTML += this.createProductsApp();
}
createProductsApp() {
return `<div>
<div class="row row-cols-3">
<div class="col-sm"><img data-fr-src="apple.jpg" height="150" width="150"><br />
<span style="color: black;">Apple $1.00/lb</span>
<input type="checkbox" name="fruits" value="apple">
</div>
<div class="col-sm"><img src="kiwi.jpg" height="150" width="150"><br />
<span style="color: black;">Kiwi $2.00/lb</span> <input type="checkbox" name="fruits" value="kiwi">
</div>
<div class="col-sm"><img src="orange.jpg" height="150" width="150"><br />
<span style="color: black;">Orange $1.00/lb</span> <input type="checkbox" name="fruits" value="orange">
</div>
</div>
</div>
<div class="row row row-cols-3"><div class="col-sm"> </div></div>
<div class="row row-cols-3"><div class="col-sm d-flex justify-content-center">
<p><button id="productsCompBtn" class="btn btn-primary">Add to Cart</button></p></div></div>`;
}
}
window.customElements.define('products-comp', ProductsComp);
where, for brevity of discussion, I am not using Shadow DOM feature of web components. All the code for this frontend is stored in its own repository.
and deployed to its own sub-domain:
https://mapteb.github.io/micro-frontends-using-webcomponents/productsApp.html
Micro Frontend #2 - Cart App
The cartApp.html
uses a web component, CartComp with a custom element like:
<cart-comp id="cartApp" ></cart-comp>
The source for cart-comp.js
is:
x
class CartComp extends HTMLElement {
constructor() {
super();
this.addEventListener('cartLoaded', e => {
this.innerHTML = this.getCartDoc(e.detail);
});
}
getCartDoc(detail) {
return `<ol class="list-group">${this.getLineItems(detail)}</ol>`;
}
getLineItems(detail) {
let lineItems = '';
for (let s of detail) {
lineItems += `<li class="list-group-item"><img data-fr-src="${s}.jpg" alt="${s}" height="20" width="20">${s}</li>`;
}
return lineItems;
}
}
window.customElements.define('cart-comp', CartComp);
All the code for this frontend is stored in its own repository.
and deployed to its own sub-domain:
https://ns-code.github.io/micro-frontends-cartapp/cartApp.html
Frontends Assembly - SPA
The two frontends are assembled as one SPA in this index.html:
x
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A layout example that shows off a responsive product landing page.">
<title>SPA with two micro-frontends</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<style>
#dialog {
width: 300px;
}
.right {
float: right
}
</style>
<body>
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
<a class="navbar-brand" href="#">Welcome to Micro-Frontends Demo</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true"> </a>
</li>
</ul>
<button class="btn btn-primary" data-toggle="modal" data-target="#staticBackdrop" onclick="">Cart (<span
id="itemsCount"></span>)</button>
</div>
</nav>
<!-- Modal -->
<div class="modal fade" id="staticBackdrop" data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Cart Items</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<cart-comp id="cartapp"></cart-comp>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<main role="main" class="container">
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-sm">
<p>Please use Chrome or Firefox to view this page.</p>
<h3>Select one or more Fruits</h3>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-sm">
<p>
<products-comp id="productsApp"></products-comp>
</p>
</div>
</div>
</div>
</div>
</main>
<section>
<div>
<section id="productsAppView">
<span>
</span>
</section>
</div>
</section>
<script>
document.getElementById("productsApp").addEventListener('selectedFruits', e => {
if (e.detail != null && e.detail.length > 0) {
document.getElementById("cartapp").dispatchEvent(
new CustomEvent('cartLoaded', {
detail: e.detail
})
);
let elem = document.getElementById("itemsCount");
let count = elem.innerText;
elem.innerText = '' + e.detail.length;
}
});
</script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<!-- web components -->
<script src="products-comp.js"></script>
<script src="https://ns-code.github.io/micro-frontends-cartapp/cart-comp.js">
</script>
</body>
</html>
where custom elements from the two frontends are used. The JavaScript files for the two frontends are also imported. When the user selects one or more fruits and adds them to the cart, the ProductsComp dispatches an event called selectedFruits. The JavaScript listener in index.html receives it and dispatches the data to another custom event called cartLoaded. The Cart App listener then receives this event data and displays it as items in the cart.
Here is the online demo for this SPA.
Conclusions
Modern browsers supported vanilla web components and event handling mechanisms are shown to provide a simple way to get started with micro frontends. The online demo hints at the many touted benefits of using micro frontends - multiple teams working in parallel and independently supporting complex SPAs.
Related Works
Interested readers can checkout the following:
1. https://micro-frontends.org/ (online demo)
2. https://github.com/kito99/micro-frontends-demo
3. https://luigi-project.io/
4. https://piral.io/
5. https://www.mashroom-server.com/
Opinions expressed by DZone contributors are their own.
Comments