Micro Frontend Architecture and Best Practices - XenonStack

What is Micro Frontend?

Micro Frontend is a Microservice approach to front-end web development. The current trend is to build a powerful and feature-rich web application which resides on top of a Microservice architecture. Over time the front end part of the application, is often developed by a separate team, grows and gets more difficult to maintain, this type of application is called Frontend Monolith. To solve this problem, the concept of the Micro Frontend came into the picture.

The concept of Micro Frontend is to think about a web application as a composition of features owned by different independent teams. Each team having a distinct area of business that it specializes. A team is cross-functional and develops end-to-end features, from the user interface to database. Micro Frontend is more friendly and less bulky one.

In this type of architecture, split the entire application by business domain across the entire stack. This enables front-end teams the same level of flexibility, testability, and velocity that backend teams get from Microservices.


How Micro Frontend Works?

Micro Frontend Techniques, strategies and recipes to build a modern web application with multiple teams using different JavaScript frameworks.

The main Concept behind Micro Frontend is as follows –

Be Technology Independent – Each team should choose and upgrade stack without having to coordinate with other teams. Custom elements help to hide implementation details while providing a neutral interface to others.

Isolate Team Code – Never share a runtime, even if teams use the same framework. Build an independent application self-contained. Do not rely on shared state or global variables.

Create Team Prefixes – Use naming conventions where isolation not possible yet. Namespace CSS, Local Storage, Events and Cookies to avoid collisions and clarify ownership.

Favor Native Browser Features over Custom APIs – Instead of building a global PubSub system use browser events for communication. If there is a need to build a cross-team API, try to keep as simple as possible.

Build a Web design that is Resilient – The features should be useful, even if JavaScript failed or do not get executed. To improve perceived performance use universal rendering and progressive enhancement.


Best Practices Of Micro-Frontend

Different practices to implement Micro Frontend –

The Single-SPA meta-framework to combine multiple frameworks on the same page without refreshing the page such a React, Vue, Angular 1, Angular 2 etc.

Multiple single-page application lives at different URLs. For shared functionality application use NPM or Bower components.

Isolating Micro-apps into Iframes using window. post Message APIs and libraries to coordinate. IFrames share APIs exposed by their parent window.

Different modules to communicate over a shared events bus. Each module is built using its own framework, as long as it handles incoming and outgoing events.

Component Libraries depending on the stack of the main app the different components and app sections developed as libraries and “required” into the main app so the main app is a composition of different components.

Web components as an integration layer.


How to Adopt Micro Frontend?

Implementing Microservices with Web components –

Integration in the Browser

Web components provide a way to create fragments of Front-End imported into Web applications. Those fragments can be packaged into Microservices together with the back-end. Services built, completed with both logic and visual representation packed together. By using this approach, Front-End applications reduced to routing makes decisions involving which set of components displayed and orchestration of events between different web components.

Web Components

Web components allow the creation of reusable components imported into Web applications. These are like widgets imported into any Web page. These are currently supported in browsers such as Chrome, Opera and FireFox. If in case, the browser does not support web components natively, compatibility accomplished using JavaScript Polyfills.

Web components consist of 4 main elements used separately or all together –

  • Custom Elements
  • Shadow DOM
  • HTML Imports
  • HTML Templates

Custom Elements

Create custom HTML tags and elements with Custom Elements. Each element has its own CSS styles and scripts. Create own tags, apply CSS styles and add behaviors through scripts. The only standard required to put hyphen to avoid conflicts new HTML elements. For example, create a list of check-out, both with custom elements and custom tags results in –



<check-out-list></check-out-list>	

In Web components, element lifecycle callbacks are there. These lifecycle callbacks allow defining behaviors specific to the component developing.

Lifecycle callbacks with custom elements are as follows –

  • CreatedCallback – It defines behavior that occurs when the component registered.
  • AttachedCallback – It defines behavior that occurs when the component inserted into the DOM.
  • DetachedCallback – It defines behavior that occurs when the element removed from the DOM.
  • AttributeChangedCallback – It defines behavior that occurs when an attribute added, changed or removed.

Example –


class CheckoutBasket extends HTMLElement {

constructer (){...} is created

connectedCallback (){...}  attached to DOM

attributeChangedCallback (attr , oldVal , newVal)  someone changed an attribute

disconnecteCallback () {...}  removed from DOM, cleanup events that have been registered
}

Custom elements by default – stencil, svelte, SkateJS, AngularElements, hyperHTML Elements etc.

Shadow DOM – the DOM is the API

Shadow DOM combines HTML, CSS and JavaScript inside a Web Component separated from the DOM of the main document when these are inside a component. This separation is similar to the one user while building API services and consumer of an API service does not know about its internals, the only thing that matters for a consumer are API requests. Such service does not have access to the outside world except to make requests to APIs of other services. Similar features observed in web components. Their internal behavior not accessed outside, except when allowed by design nor affects the DOM document they reside in. The main method of communication between web components is by firing events.

HTML Imports

For web components, HTML imports are the packaging mechanism. HTML imports tell DOM, the location of a Web Component. In the context of Microservices, import remote location of a service contains the component to use.

HTML imports is a method to reuse and include HTML documents via other HTML documents. Predefined components as HTML imports, where each of them include own styles and scripts, decide on the top level which HTML import present in DOM at the moment, and the imported document handles rest of things.

Shell – Shell is a top-level wrapper which consists of a container for component and component picker. It should include controllers or views which allow user manipulating components.

Container – Container is the actual root place where HTML code of nested applications should be injected. For all nested apps it should have a single entry point.

Component picker – Component picker allows managing of nested applications active at the moment.

HTML imports – HTML imports are the abstract Microservices. These could be whole apps written in different frameworks.

HTML Templates

The HTML template element holds client-side content not rendered when a page loaded. It's instantiated through JavaScript. It is a fragment of code used in the document.

Let's try to understand Implementation of Micro Frontend with the following React Js app example –

It is good when web application developed independently so that changes to the element done without being blocked by others and without breaking others as well. That's why in this example new react app needed to build, run, and deploy separately, treating others that have to communicate with it as services.

The example below creates a header for a web page. In this example, React.js used. The modern generation is using it nowadays. Let's use create-react-app for fast bootstrap –


npm install -g create-react-app

create-react-app head

cd head/

npm start

Now let's add server-side rendering real quick, It will be used later when joining apps, for SEO and performance reasons. It is easier to attach this at the beginning.


Change src/App.js to an actual header:

import React from 'react';

export default () =>


<header>

<h1>Logo</h1>
<nav>
<ul>
<li>About</li>
<li>Contact</li>
</ul>
</nav>
</header>;

 

Then, create a file called server.js at the root of the project that will start an express server and server-side render react –


const path = require('path');

const fs = require('fs');

const express = require('express');

const React = require('react');

const App = require('./transpiled/App.js').default;

const { renderToString } = require('react-dom/server');

const server = express();

server.get('/', (req, res) => {

const htmlPath = path.resolve(__dirname, 'build', 'index.html');

 fs.readFile(htmlPath, 'utf8', (err, html) => {


const rootElem = '<div id="root">';

const renderedApp = renderToString(React.createElement(App, null));
res.send(html.replace(rootElem, rootElem + renderedApp));
});
});
server.use(express.static('build'));
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`App listening on port ${port}`);
});

This script takes the root to react element (App), renders it to a string, and push it into the HTML before serving it to the user. React will later mount on top of that already rendered component.

But, that runs on NodeJS, and Node JS doesn't understand JSX or other newer syntaxes like import, so babel used to transpile it before running the server –


npm install --dev babel-cli babel-preset-es2015

Add two tasks to the scripts section in package.json to run that –


"transpile": "NODE_ENV=production babel src --out-dir transpiled --presets es2015,react-app",

"start:prod": "NODE_ENV=production node server.js"

 

That's it, Now run the header with –


npm run build

npm run transpile

npm run start:prod

Why Micro Frontend Matters?

In Modern Era, new web apps, the front end is becoming bigger and bigger, and the back end is getting less important. Most of the code written on the frontend. And the Monolith approach doesn't work for a larger web application. A monolithic approach to a large front-end application becomes unwieldy. There needs to be a tool of breaking it up into smaller modules that act independently. The solution to the problem is Micro frontend. As Micro Frontend code written only in pure JavaScript and any of the javascript frameworks used or migrated from one framework to another.


Benefits Of Micro Frontend

Every day a new JavaScript technology invented and these are increasing faster than a speeding bullet. That sometimes it can be frustrating as every JavaScript technology has its own pros and cons. And while selecting a particular technology, everyone considers the maximum benefit and minimum risk. Micro frontend helps to use different technology for different services.

Here are some benefits of Micro Frontend –

  • Support code and style isolation, an individual development team can choose their own technology The development and deployment speed is very fast.
  • It helps in Continuous Deployment.
  • The testing becomes very simple as well as for every small change, don’t have to go and touch the entire application.
  • Front-end renovation – Improving new things becomes easier.
  • High Resilience and Better Maintenance.
  • Support code and style isolation.

Concluding Micro Frontend

Make sure the core and the integration are as simple as possible. One of the critical problems is to standardize the UI/UX principles. A universal solution is to use a style guide, example – Material Design, Bootstrap, among others. Communication with the team is the key to ensure everything is running smooth, create some standards and rules to minimize conflicts with the difference of teams working on a product. All these solutions meant to solve one problem, scalability. An application tends to grow significantly introduces numerous issues and conflicts, splitting the code into teams and applying the right logistic to deliver good quality, technology trending, and fast solutions to the world.