Micro Frontend Architecture and Module Federation: A Deep Dive

In the modern web development landscape, scalability, maintainability, and faster deployment are critical. Micro Frontend Architecture has emerged as a powerful solution to address these needs, especially for large-scale applications. This blog explores Micro Frontend Architecture and delves into Module Federation, a key feature in Webpack 5 that facilitates micro frontends.


What is Micro Frontend Architecture?

Micro Frontend Architecture applies the micro services approach to frontend development. Instead of building a monolithic frontend, applications are split into smaller, independently deployable units called micro frontends. Each micro frontend is responsible for a specific feature or functionality and can be developed and maintained by different teams.

Benefits of Micro Frontend Architecture:

  1. Independent Development: Teams can work on different parts of the application without interfering with each other.

  2. Scalability: Easier to scale specific features rather than the entire application.

  3. Technology Agnostic: Each micro frontend can use different technologies, frameworks, or libraries.

  4. Faster Deployment: Individual features can be deployed independently, reducing downtime.


What is Module Federation?

Module Federation, introduced in Webpack 5, is a feature that allows JavaScript applications to dynamically share and consume modules at runtime. It simplifies the implementation of micro frontends by enabling seamless integration of independently deployed frontend modules.

Key Features of Module Federation:

  1. Dynamic Module Sharing: Share components or libraries between applications without bundling them together.

  2. Version Independence: Consume different versions of a shared module in different parts of the application.

  3. Runtime Integration: Modules are fetched and integrated at runtime, reducing the need for large initial bundles.


Setting Up a Micro Frontend with Module Federation

Example: Building a Host and Remote Application

Let’s create a simple example where we have two applications:

  • Host Application: Integrates a micro frontend.

  • Remote Application: Provides a micro frontend.


Step 1: Create Two React Applications

Use create-react-app or Vite to set up two React applications.

npx create-react-app host-app
npx create-react-app remote-app

Step 2: Configure Webpack with Module Federation

Install Webpack and Module Federation dependencies in both projects:

npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
npm install @module-federation/webpack-5

In the webpack.config.js of each project, configure Module Federation.

Remote Application (remote-app/webpack.config.js):

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index",
  mode: "development",
  devServer: {
    port: 3001,
  },
  output: {
    publicPath: "http://localhost:3001/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "remoteApp",
      filename: "remoteEntry.js",
      exposes: {
        "./Header": "./src/components/Header",
      },
      shared: { react: { singleton: true }, "react-dom": { singleton: true } },
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
};

Host Application (host-app/webpack.config.js):

const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index",
  mode: "development",
  devServer: {
    port: 3000,
  },
  output: {
    publicPath: "http://localhost:3000/",
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "hostApp",
      remotes: {
        remoteApp: "remoteApp@http://localhost:3001/remoteEntry.js",
      },
      shared: { react: { singleton: true }, "react-dom": { singleton: true } },
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
};

Step 3: Create Shared Components

Remote Application (remote-app/src/components/Header.js):

import React from 'react';

const Header = () => {
  return <h1>Welcome to the Remote Application</h1>;
};

export default Header;

Host Application (host-app/src/App.js):

import React from 'react';
const RemoteHeader = React.lazy(() => import('remoteApp/Header'));

const App = () => {
  return (
    <div>
      <h1>Host Application</h1>
      <React.Suspense fallback="Loading...">
        <RemoteHeader />
      </React.Suspense>
    </div>
  );
};

export default App;

Step 4: Run Both Applications

Start both applications:

cd remote-app && npm start
cd host-app && npm start

Open the Host Application in the browser (localhost:3000), and you should see the remote component integrated seamlessly.


Challenges and Best Practices

Challenges:

  • Dependency Conflicts: Shared dependencies can lead to version mismatches.

  • Performance Overhead: Runtime integration can increase load times if not optimized.

  • Communication: Managing communication between micro frontends can be complex.

Best Practices:

  1. Define Clear Boundaries: Each micro frontend should own specific functionality.

  2. Optimize Shared Modules: Use singleton versions of shared libraries like React.

  3. Version Management: Maintain a consistent versioning strategy for shared dependencies.

  4. Testing: Test each micro frontend independently and as a whole.


Conclusion

Micro Frontend Architecture and Module Federation bring flexibility and scalability to frontend development. By dividing an application into smaller, manageable pieces, development becomes faster and more efficient. Module Federation further simplifies integration, allowing teams to work independently while ensuring seamless user experiences. With careful planning and adherence to best practices, this architecture can significantly enhance your development workflow.


Happy Coding!