Micro-Frontend Architecture (MFE) is an approach to frontend development where a larger frontend application is divided into smaller, loosely coupled, and independently deployable pieces, each managed by different teams or microservices. Webpack’s Module Federation plugin is widely used in the context of MFEs in React to achieve this modularization by enabling multiple projects to share code at runtime.

Here’s an in-depth look at using Micro-Frontend Architecture (MFE) with Webpack Module Federation in a React application.

1. Concept of Micro-Frontend Architecture (MFE)

In an MFE setup, each micro-frontend can be thought of as a separate application or module responsible for a specific feature or set of functionalities. This architecture enables independent development, testing, deployment, and scaling of each micro-frontend.

Advantages of MFE with React and Webpack Module Federation:

  • Independent Deployment: Each micro-frontend can be deployed separately.
  • Scalability: Large applications can be divided into smaller, more manageable modules.
  • Technology Flexibility: Different teams can use different technologies or libraries.
  • Reduced Build Times: Changes to a single micro-frontend don’t require a rebuild of the entire application.

2. Webpack Module Federation for MFE

Webpack’s Module Federation plugin allows multiple applications to share code between each other at runtime. Using Module Federation, an app can import components from another remote app as if they were local components, with Webpack managing the loading and bundling of these remote components on demand.

3. Setting up an MFE with Webpack Module Federation in React

In this example, we’ll create two micro-frontends:

  • Host Application (host): The main application that consumes micro-frontends.
  • Remote Application (remote): The micro-frontend, which exposes a component to the host application.

4. Step-by-Step Example

Step 1: Install Dependencies

For both applications, install webpack, webpack-cli, webpack-dev-server, and @babel/preset-react for handling React files.

# Install dependencies for both apps
npm install webpack webpack-cli webpack-dev-server @babel/core babel-loader @babel/preset-react react react-dom

Step 2: Configure Webpack Module Federation for Both Apps

  1. Remote Application Configuration (Remote MFE)
  • In the remote app, set up Webpack to expose a component (for example, Button component).
   // remote/webpack.config.js
   const path = require('path');
   const { ModuleFederationPlugin } = require('webpack').container;

   module.exports = {
     entry: './src/index.js',
     mode: 'development',
     output: {
       publicPath: 'http://localhost:3001/', // Ensure this matches the remote's dev server port
       path: path.resolve(__dirname, 'dist'),
     },
     devServer: {
       port: 3001,
       contentBase: path.join(__dirname, 'dist'),
     },
     module: {
       rules: [
         {
           test: /\.jsx?$/,
           use: 'babel-loader',
           exclude: /node_modules/,
         },
       ],
     },
     plugins: [
       new ModuleFederationPlugin({
         name: 'remoteApp',
         filename: 'remoteEntry.js',
         exposes: {
           './Button': './src/Button', // Expose the Button component
         },
         shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
       }),
     ],
   };
  • In remote/src/Button.js, create a simple component:
   // remote/src/Button.js
   import React from 'react';

   const Button = () => {
     return <button>Remote Button</button>;
   };

   export default Button;
  • The entry point remote/src/index.js:
   // remote/src/index.js
   import('./Button'); // Just to initialize the remote module
  1. Host Application Configuration
  • In the host application, configure Webpack to consume the exposed component from the remote app.
   // host/webpack.config.js
   const path = require('path');
   const { ModuleFederationPlugin } = require('webpack').container;

   module.exports = {
     entry: './src/index.js',
     mode: 'development',
     output: {
       publicPath: 'http://localhost:3000/',
       path: path.resolve(__dirname, 'dist'),
     },
     devServer: {
       port: 3000,
       contentBase: path.join(__dirname, 'dist'),
     },
     module: {
       rules: [
         {
           test: /\.jsx?$/,
           use: 'babel-loader',
           exclude: /node_modules/,
         },
       ],
     },
     plugins: [
       new ModuleFederationPlugin({
         name: 'hostApp',
         remotes: {
           remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
         },
         shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
       }),
     ],
   };
  • Import the remote Button component in the host application’s entry file host/src/index.js:
   // host/src/index.js
   import React from 'react';
   import ReactDOM from 'react-dom';

   const App = React.lazy(() => import('remoteApp/Button')); // Load remote Button component

   const Root = () => (
     <React.Suspense fallback="Loading Remote Component...">
       <App />
     </React.Suspense>
   );

   ReactDOM.render(<Root />, document.getElementById('root'));
  • The host application needs an HTML file host/public/index.html:
   <!DOCTYPE html>
   <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <title>Host App</title>
   </head>
   <body>
     <div id="root"></div>
     <script src="bundle.js"></script>
   </body>
   </html>

Step 3: Running the Applications

Start the remote and host applications using webpack-dev-server.

# Start the remote app
cd remote
npx webpack serve

# In a separate terminal, start the host app
cd host
npx webpack serve

Visit http://localhost:3000 in a browser to see the host app, which should display the button component from the remote app.

Summary

This MFE setup with Webpack Module Federation enables a host app to dynamically load components from a remote app, creating a modular, independently deployable frontend system. With this approach, each app can evolve independently, and changes in one do not require redeployment of the other.