DEV Community

Shashank Trivedi
Shashank Trivedi

Posted on

Webpack 5 Series part-3

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:

  1. ProductList exposes a list of products that can be imported and used by other apps.
  2. Cart exposes functionality to add/remove products from the cart.
  3. Checkout uses the data from Cart and processes the checkout.

Image description

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 }, }), ], }; 
Enter fullscreen mode Exit fullscreen mode
  • 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 }, }), ], }; 
Enter fullscreen mode Exit fullscreen mode
  • 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 }, }, }), ], }; 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode
  • 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; 
Enter fullscreen mode Exit fullscreen mode
  • 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; 
Enter fullscreen mode Exit fullscreen mode

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)