Understanding Micro frontends: A Comprehensive Guide

Gohilnancy
Simform Engineering
7 min readApr 23, 2024

Navigating the future: Dive into micro frontend architecture

In the realm of web development, the pursuit of scalable, agile, and maintainable solutions has led to the emergence of innovative architectural paradigms. Micro frontend architecture stands at the forefront of this evolution, offering a revolutionary approach to designing and building frontend applications.

Traditionally, frontend development involved managing monolithic codebases, where features were tightly coupled within a single application. While functional, this approach posed challenges in scalability, team collaboration, and deployment agility. Micro frontends break down this monolithic structure into smaller, independent pieces, akin to solving a large puzzle.

At its core, micro frontend architecture lies in the principles of modularity and encapsulation. One of its key advantages is the ability of micro frontends to be developed, tested, deployed, and scaled independently.

Source: cygnismedia

Table of content

Pros of Micro frontends

The following are some of the advantages of using Micro frontend:

  1. Independent Development: Teams can work independently on different application parts, leading to faster development cycles and reduced coordination overhead.
  2. Scalability: Micro frontends allow horizontal scaling, enabling teams to scale individual application parts as needed.
  3. Reusability: Components and functionality within micro frontends can be reused across different application parts, reducing duplication of effort and maintenance overhead.
  4. Easier Deployment: Micro frontend architectures simplify the deployment process by decoupling frontend components from each other, enabling faster time-to-market.

Cons of Micro frontends

  1. Increased Complexity: Managing multiple independent frontend applications, each with its own codebase, dependencies, and deployment pipeline, can be challenging and require additional tooling and infrastructure.
  2. Versioning and Compatibility: Ensuring that all micro frontends are compatible with each other and the container application requires careful planning and coordination, which can be challenging.
  3. Cross-Cutting Concerns: Handling cross-cutting concerns such as authentication, authorization, and routing requires careful planning.

Micro frontend vs. Monolithic Architecture

  • Flexibility: Micro frontend architecture offers more flexibility in technology choices, development processes, and deployment strategies than monolithic architecture.
  • Scalability: Micro frontend architecture enables more granular scaling and resource allocation, improving performance and cost efficiency.
  • Complexity: While offering modularity, micro frontend architecture adds complexity to communication and management.
  • Maintenance: Micro frontend architecture can simplify maintenance and updates by isolating changes to individual micro frontends, but it requires careful coordination and planning to ensure consistency and compatibility across micro frontends.
  • Deployment: Micro frontend architecture allows faster deployments but requires more sophisticated deployment pipelines and tooling to manage multiple independent deployments.
Source: trulioo.com

File Structure

Here is an example of the file structure of an entire application. It includes a container in which all other micro apps are called, and the other three are the micro apps: header, menu, and cart.

my-microfrontend-app/

├── menu/
│ ├── src/
│ │ ├── components/
│ │ │ ├── MenuList.vue
│ │ │
│ │ ├── store
│ │ ├── App.vue
│ │ ├── main.js
│ │ └── pubsub.js
│ ├── package.json
│ └── vite.config.js

├── cart/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Cart.vue
│ │ ├── store
│ │ ├── App.vue
│ │ ├── main.js
│ │ └── pubsub.js
│ ├── package.json
│ └── vite.config.js

├── container/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Home.vue
│ │ ├── router
│ │ ├── App.vue
│ │ ├── main.js
│ │ └── pubsub.js
│ ├── package.json
│ └── vite.config.js

├── header/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Header.vue
│ │ ├── App.vue
│ │ └── main.js
│ ├── package.json
│ └── vite.config.js


└── package.json

Info about config and pubsub is explained separately below.

Understanding Module Federation

Module Federation, introduced in Webpack 5, enables dynamic code sharing between multiple applications at runtime. It enables building micro frontend architectures where multiple frontend applications can be developed, deployed, and maintained independently while sharing common dependencies.

vite-plugin-federation is a Vite plugin that provides similar functionality to Webpack's Module Federation but is tailored for use with Vite. It allows us to configure and set up Module Federation in our Vite projects, enabling us to create modular and scalable frontend architectures.

With vite-plugin-federation, we can define remotes, expose modules, and share dependencies between different Vite applications, allowing for efficient code sharing and dynamic loading of remote modules at runtime.

Setup Project

Step 1: Set up the Container Application

  1. Create a new directory for the container application:
mkdir container-app
cd container-app

2. Initialize a new Vite project for the container application:

npm init @vitejs/app

3. When prompted, choose “vue” or any other technology as the project template.

4. Install vite-plugin-federation and vue-router:

npm install vite-plugin-federation vue-router

5. Create a vite.config.js file at the root of the project:

// vite.config.js
import { defineConfig } from 'vite';
import Vue from '@vitejs/plugin-vue';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
Vue(),
federation({
name: 'container',
remotes: {
'cart': 'http://localhost:3001/dist/assets/cart.js',
'menu': 'http://localhost:3002/dist/assets/menu.js',
'header': 'http://localhost:3003/dist/assets/header.js',
},
}),
],
});

In the above code, a build is created in all the applications using npm run build, and after that, a dist folder is created in each app. We have to use the path of each dist folder with the URL of each application to integrate all apps together. This can be done similarly for any framework, whether vuejs, reactjs, or angular.

Step 2: Set up the Micro frontend Applications (Menu, Cart, Header)

For each micro frontend application (Menu, Cart, Header), repeat the following steps:

  1. Create a new directory for the micro frontend:
mkdir cart
cd cart

2. Initialize a new Vite project for the micro frontend:

npm init @vitejs/app

3. Choose “vue” as the project template when prompted.

4. Install vite-plugin-federation:

npm install vite-plugin-federation

5. Create a vite.config.js file at the root of the cart app:

// vite.config.js
import { defineConfig } from 'vite';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'cart',
filename: 'cart.js',
exposes: {
'./App': './src/App.vue',
},
}),
],
});

Repeat this step for the Header and Menu App with the files that have to be exposed to other apps.

vite.config.js file at the root of the Container app:

// vite.config.js
import { defineConfig } from 'vite';
import federation from 'vite-plugin-federation';
export default defineConfig({
plugins: [
federation({
name: 'container',
filename: 'container.js',
remotes: {
'cart': 'http://localhost:3001/cart.js',
// Add more remotes as needed
},
exposes: {
'./App': './src/App.vue',
},
}),
],
});

Repeat this step for the Header and Menu Apps, changing the port number and remote name accordingly. The remote name should be similar to the individual file name mentioned in each app's config file.

Exposes: Exposing a module means making it available for consumption by other micro frontends. When a micro frontend exposes a module, it declares certain components, utilities, or functionalities available for use by other micro frontends.

Remote: Remoting a module means importing and using modules that are exposed by other micro frontends. When a micro frontend remotely imports a module, it retrieves and uses components, utilities, or functionalities exposed by other micro frontends.

Step 3: Run the Applications

  1. Start each micro frontend application (Header, Cart, Menu):
cd header 
npm run dev

Repeat for Cart and Menu.

2. Start the container application:

cd container
npm run dev

Inter-App Communication

To communicate between the apps, there needs to be a trigger event so that while passing data from one app to another, apps know when the data is passed. Publish/Subscribe is the best way for the event to trigger between apps. Follow the same as below:

Create a pubsub.js in src folder of all apps. The js file can be named at your convenience.

// src/pubsub.js
const globalListenersKey = '__pubsub_listeners__';
const getGlobalListeners = () => {
if (!window[globalListenersKey]) {
window[globalListenersKey] = {};
}
return window[globalListenersKey];
};
export const pubsub = {
subscribe: (event, callback) => {
const listeners = getGlobalListeners();
if (!listeners[event]) {
listeners[event] = [];
}
listeners[event].push(callback);
},
publish: (event, data) => {
const listeners = getGlobalListeners();
if (listeners[event]) {
listeners[event].forEach((callback) => callback(data));
}
},
};
  1. globalListenersKey: This variable defines a unique key to store the global listeners in the window object. This key is used to access the global listeners object.
  2. getGlobalListeners: This function retrieves the global listeners object from the window object. If the listeners object does not exist yet, it takes an empty object as default.
  3. pubsub object: This object contains two methods:
  • subscribe: This method is used to subscribe to a specific event. It takes two parameters: event (the name of the event to subscribe to) and callback (the function to be called when the event is published.) It retrieves the global listeners object, creates an array for the specified event if it doesn't exist yet, and adds the provided callback function to that array.
  • publish: This method is used to publish a message to all subscribers of a specific event. It takes two parameters: event (the name of the event to publish) and data (the data to be passed to the subscribers). It retrieves the global listeners object, checks for subscribers for the specified event, and, if so, iterates over the array of callbacks for that event, calling each callback function with the provided data.

Let’s see how we can call the publish and subscribe method with an example in which we will publish a method in the Menu app and subscribe in the container app:

//Menu app
pubsub.publish("addToCart", data);
//container app
pubsub.subscribe("addToCart", (product) => {
console.log(product)
});

Conclusion

Embracing micro frontend architecture revolutionizes development, enabling agility, scalability, and rich user experiences. With flexibility and collaboration, micro frontends pave the way for the future of web development.

Micro frontend is an amazing architecture for complex projects and teams, but it requires careful consideration of its drawbacks. Your evaluation and strategic implementation will lead to a successful journey. Enjoyed the read? Reach out with any questions!

Implementation demo: github.com/Nancyy0912/Vue-Microfrontend-Demo

For more updates on the latest tools and technologies, follow the Simform Engineering blog.

Follow us: Twitter | LinkedIn

--

--