Webpack 5 Series part-3
Shashank Trivedi
Posted on September 15, 2024
Please look for the old part of the series to fully understand the concept.
Online E-Store Application
Let's build an Online Store application using micro-frontends for modularity. Each micro-frontend will represent a different part of the store, and they will share common libraries like React, a design system, and a shared utility library.
Goal:
- ProductList exposes a list of products that can be imported and used by other apps.
- Cart exposes functionality to add/remove products from the cart.
- Checkout uses the data from Cart and processes the checkout.
Configuration for Module Federation
- Micro-Frontend 1: ProductList
Exposes the ProductList component for other micro-frontends to use.
// webpack.config.js (ProductList)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'productListApp',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/ProductList',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
// Share any other libraries like a UI library, e.g., Material-UI
},
}),
],
};
- Micro-Frontend 2: Cart
Exposes the Cart component, and it uses a shared state library (like Zustand) for cart management.
// webpack.config.js (Cart)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'cartApp',
filename: 'remoteEntry.js',
exposes: {
'./Cart': './src/Cart',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
zustand: { singleton: true }, // Zustand or Redux for shared state
},
}),
],
};
- Micro-Frontend 3: Checkout
Consumes the Cart and ProductList components to show a summary before checkout.
// webpack.config.js (Checkout)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'checkoutApp',
remotes: {
productListApp: 'productListApp@http://localhost:3001/remoteEntry.js',
cartApp: 'cartApp@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true },
},
}),
],
};
Component Implementation:
- Micro-Frontend 1: ProductList
Exposed by ProductList micro-frontend.
// src/ProductList.js (ProductList)
import React from 'react';
const products = [
{ id: 1, name: 'Product 1', price: 50 },
{ id: 2, name: 'Product 2', price: 75 },
];
const ProductList = () => (
<div>
<h2>Products</h2>
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
</div>
);
export default ProductList;
- Micro-Frontend 2: Cart
Exposed by Cart micro-frontend and manages state (e.g., using Zustand or Redux).
// src/Cart.js (Cart)
import React from 'react';
import create from 'zustand';
// Zustand store for managing the cart
const useCartStore = create(set => ({
cart: [],
addToCart: (product) => set(state => ({ cart: [...state.cart, product] })),
removeFromCart: (product) =>
set(state => ({ cart: state.cart.filter(item => item.id !== product.id) })),
}));
const Cart = () => {
const { cart, addToCart, removeFromCart } = useCartStore();
return (
<div>
<h2>Cart</h2>
<ul>
{cart.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
<button onClick={() => removeFromCart(product)}>Remove</button>
</li>
))}
</ul>
</div>
);
};
export default Cart;
- Micro-Frontend 3: Checkout
Consumes Cart and ProductList components, bringing everything together.
// src/Checkout.js (Checkout)
import React, { lazy, Suspense } from 'react';
const ProductList = lazy(() => import('productListApp/ProductList'));
const Cart = lazy(() => import('cartApp/Cart'));
const Checkout = () => (
<div>
<h1>Checkout</h1>
<Suspense fallback={<div>Loading Products...</div>}>
<ProductList />
</Suspense>
<Suspense fallback={<div>Loading Cart...</div>}>
<Cart />
</Suspense>
<button>Proceed to Payment</button>
</div>
);
export default Checkout;
Steps to Run the App:
- Run the Micro-Frontends:
Each micro-frontend (ProductList, Cart, Checkout) will be served on different ports (e.g., ProductList on localhost:3001, Cart on localhost:3002, and Checkout on localhost:3003).
You will need to set up each micro-frontend with Webpack dev server and run them individually.
- Consume Remote Modules:
In the Checkout micro-frontend, we are dynamically importing the ProductList and Cart components from their respective remote micro-frontends.
- Shared Dependencies:
Each micro-frontend shares React, React-DOM, and possibly other shared dependencies like a state library (e.g., Zustand) or a design system (Material-UI).
Posted on September 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.