In enterprise-level applications, working on an application from a single code base turns out to be a massive head-ache. For this purpose, Micro-Frontend architecture was born. Let's dive into the world of Micro-Frontend and discuss some of the nitty-gritty of following this approach.
What is Micro-Frontend?
Let's check the definition a google search spits out:
Micro-frontend architecture is a design approach in which a front-end app is decomposed into individual, independent βmicro-appsβ working loosely together.
There are two major parts of a Micro-frontend application:
- Container
- Sub-Applications
The container determines when each of the sub-application should be displayed on the screen. All the business logic is handled by each of the sub-applications.
Why use Micro-Frontend?
There are numerous benefits of using Micro-Frontend architecture:
- You can use different framework in each sub-application
- Modification or even bugs in one sub-application has no effect on the other sub-applications
- You can run A/B Tests easily to maximize customer conversions
- Makes it easier for teams to collaborate on a project (can be hosted as separate repository for each sub-app or a mono-repo)
- and many more
Key Principles of Micro-Frontend
There are two strict requirements for this architecture:
- The micro apps should work absolutely independently of each other, for example, the auth sub-app should not in any way rely on data from the product sub-app
- The micro apps can communicate with the container, but the communication should be kept as minimal as possible and should be done in as generic means as possible. So, even if both the container and a sub-app uses the same framework, let's say React, they should not pass React components between them, but some generic functions and objects. This ensures that during a major refactor of either the micro apps or the container, we don't have to work on the other.
Basic Micro-Frontend app
Ok, enough talk! Now it's time to get our hands dirty making a basic Micro-frontend app.
Let's make three folders for:
- container
- cart
- products
We would use faker
to generate fake data for the cart and products. To install the library, open the folder in the command prompt, initialize a node project using npm init
and use npm install faker
.
Implementing Micro-Frontend would be a nightmare for pretty much everyone, but luckily we have Module Federation plugin available with webpack which makes it a piece of cake. To install all webpack associated packages, use (in each folder):
npm install -D webpack webpack-cli html-webpack-plugin webpack-dev-server
Add public/index.html
in all three folders
<!-- cart -->
<div id="dev-cart"></div>
<!-- products-->
<div id="dev-products"></div>
<!-- container-->
<div id="product-list"></div>
<hr />
<div id="cart-items"></div>
Now setting up our webpack.config.js
:
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devServer: {
port: 3000,
},
plugins: [
new HTMLWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
})
],
};
Copy the webpack.config.js
at the root of each folder, but make sure you have different port numbers for the dev server.
Now let's set up the Module Federation
:
// cart/webpack.config.js
// ...
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const packageJson = require('./package.json')
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'cart', // name of the item being exposed (required in container)
filename: 'remoteEntry.js', // name of the file
exposes: {
'./Cart': './src/bootstrap' // actual file being exposed
},
shared: packageJson.dependencies, // to remove duplicate external modules loading in the container
}),
// ...
]
};
// products/webpack.config.js
// ...
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const packageJson = require('./package.json')
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'products', // name of the item being exposed (required in container)
filename: 'remoteEntry.js', // name of the file
exposes: {
'./ProductIndex': './src/bootstrap' // actual file being exposed
},
shared: packageJson.dependencies, // to remove duplicate external modules loading in the container
}),
// ...
]
};
// container/webpack.config.js
// ...
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const packageJson = require('./package.json')
module.exports = {
// ...
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
products: 'products@http://localhost:8000/remoteEntry.js', // importing products file from port 8000
cart: 'cart@http://localhost:8001/remoteEntry.js', // importing cart file from port 8001
},
}),
// ...
]
};
With webpack now setup, we need to add our cart, products and container:
// cart/src/bootstrap.js
import faker from 'faker'
const mount = (element) => {
// generating fake data
element.innerHTML = `
<p>You have ${faker.datatype.number(10)} items in your cart</p>
`
}
const mountPt = document.querySelector('#dev-cart')
if (process.env.NODE_ENV === 'development' && mountPt) {
mount(document.querySelector('#dev-cart'))
}
export { mount } // sharing generic mount function
// products/src/bootstrap.js
import faker from 'faker'
const PRODUCTS_COUNT = 5
const mount = (element) => {
// generating fake data
let productsArr = []
for (let i = 0; i < PRODUCTS_COUNT; i++) {
const product = faker.commerce.productName();
productsArr.push(`<div>${product}</div>\n`)
}
const products = productsArr.join('')
element.innerHTML = products
}
const mountPt = document.querySelector('#dev-products')
if (process.env.NODE_ENV === 'development' && mountPt) {
mount(mountPt)
}
export { mount } // sharing generic mount function
// container/src/bootstrap.js
import { mount as mountProducts } from 'products/ProductIndex'
import { mount as mountCart } from 'cart/Cart'
mountProducts(document.querySelector('#product-list'))
mountCart(document.querySelector('#cart-items'))
Now finally create an index.js
file in the src
folder of each sub-app & container
import('./bootstrap')
Creating this file with dynamic import is absolutely crucial as webpack fails to import the external packages without it (since the packages are shared, so they behave differently).
Now your app is ready. You can add the following script in the scripts
section of package.json
:
"scripts": {
"dev": "webpack serve"
}
and call npm run dev
to start up the webpack server
Products (port 8000)
Cart (port 8001)
Container & the entire app (port 3000)
Wrapping Up
In this article, we went through the basics of Micro-frontend architecture. Hope it helps you in your development journey :)
I am currently working on a project utilizing Micro-frontend architecture, feel free to check it out:
ruppysuppy / Crypto-Crowdfund
π€π° Crowdfunding Platform backed by Ethereum Blockchain to bring your creative projects to life
Finding personal finance too intimidating? Checkout my Instagram to become a Dollar Ninja
Thanks for reading
Need a Top Rated Front-End Development Freelancer? Contact me on Upwork
Want to see what I am working on? Check out my GitHub
I am a freelancer who will start off as a Digital Nomad in mid-2022. Want to catch the journey? Follow me on Instagram
Follow my blogs for Weekly new Tidbits on Dev
FAQ
These are a few commonly asked questions I get. So, I hope this FAQ section solves your issues.
-
I am a beginner, how should I learn Front-End Web Dev?
Look into the following articles: Would you mentor me?
Sorry, I am already under a lot of workload and would not have the time to mentor anyone.Would you like to collaborate on our site?
As mentioned in the previous question, I am in a time crunch, so I would have to pass on such opportunities.
Connect to me on
Top comments (0)