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)