DEV Community

Shashank Trivedi
Shashank Trivedi

Posted on

1

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).

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay