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).
Top comments (0)