# Effortless Dynamic Module Loading with Module Federation V2 in React

Module Federation V2 (MFv2) brings new runtime APIs and improved share‑scope handling to the micro‑frontend ecosystem. In this post, we’ll build a minimal React host and remote using the `@module-federation/enhanced` runtime, show full example code, and compare the pros and cons against the classic Webpack Module Federation (MF 1.x).

---

## 1\. Introduction

Micro‑frontends let teams ship independently deployable chunks of UI. Webpack Module Federation (since v5) enabled this by letting one app (the **host**) dynamically load code from another (the **remote**). However, in MF 1.x:

* Share scopes could collide when multiple hosts loaded the same remote.
    
* Dynamic remotes required manual `__webpack_init_sharing__` calls.
    
* Remote URLs were baked in at build time (hard to swap at runtime).
    

MFv2’s **enhanced runtime** solves these pain points with:

* A standalone runtime package (`@module-federation/enhanced/runtime`).
    
* A global share scope and idempotent remote initialization.
    
* Clean `init()` + `loadRemote()` APIs for dynamic loading.
    

---

## 2\. What Is Module Federation V2?

* **Build plugin:** `@module-federation/enhanced/webpack`, a drop‑in replacement for Webpack’s built‑in `ModuleFederationPlugin`.
    
* **Runtime API:** methods like `init()` and `loadRemote()` decouple runtime logic from Webpack’s bootstrap.
    
* **Global share scopes:** ensures a single shared dependency map across all hosts/remotes on the page.
    

This separation means you can register remotes once, swap URLs at runtime, and avoid duplicate `container.init` errors.

---

## 3\. Prerequisites

* Node.js ≥ 16, npm or Yarn.
    
* React 18+ (or compatible).
    
* Webpack 5.
    
* Basic familiarity with Module Federation concepts.
    

Install dependencies in both projects:

```powershell
npm install react react-dom
npm install --save-dev webpack webpack-cli webpack-dev-server babel-loader @babel/preset-react html-webpack-plugin
npm install --save @module-federation/enhanced
```

---

## 4\. Remote App Setup

### 4.1 `webpack.config.js`

```javascript
// remote/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  mode: 'development',
  entry: './src/index.jsx',
  output: {
    publicPath: 'auto',
    uniqueName: 'remote_app',
  },
  resolve: { extensions: ['.jsx', '.js'] },
  module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } ] },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new ModuleFederationPlugin({
      name: 'remote_app',
      filename: 'remoteEntry.js',
      exposes: {
        './Widget': './src/Widget.jsx'
      },
      shared: {
        react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }
      },
    }),
  ],
};
```

### 4.2 Remote Component

```jsx
// remote/src/Widget.jsx
import React, { useContext } from 'react';

export default function Widget({ message }) {
  return <div style={{ padding: 20, background: '#eef' }}>Remote says: {message}</div>;
}
```

---

## 5\. Host App Setup

### 5.1 `webpack.config.js`

```javascript
// host/webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('@module-federation/enhanced/webpack');

module.exports = {
  mode: 'development',
  entry: './src/index.jsx',
  output: {
    publicPath: 'auto',
    clean: true,
  },
  resolve: { extensions: ['.jsx', '.js'] },
  module: { rules: [ { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ } ] },
  plugins: [
    new HtmlWebpackPlugin({ template: './public/index.html' }),
    new ModuleFederationPlugin({
      name: 'host_app',
      shared: {
        react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }
      },
    }),
  ],
  devServer: { port: 3000, historyApiFallback: true },
};
```

---

## 6\. React Integration with Enhanced Runtime

In your host app’s React entry:

```javascript
// host/src/App.jsx
import React, { Suspense, lazy, useEffect, useState } from 'react';
import { init, loadRemote } from '@module-federation/enhanced/runtime';

export default function App() {
  const [Widget, setWidget] = useState(null);

  useEffect(() => {
    async function load() {
      // 1) Initialize share scope and register remote URL
      await init({
        name: 'host_app',
        remotes: [ { name: 'remote_app', entry: 'http://localhost:4000/remoteEntry.js' } ]
      });

      // 2) Lazy-load the exposed module
      const LazyWidget = lazy(() =>
        loadRemote('remote_app/Widget').then(mod => ({ default: mod.default }))
      );
      setWidget(() => LazyWidget);
    }

    load();
  }, []);

  return (
    <div>
      <h1>Host</h1>
      {Widget ? (
        <Suspense fallback="Loading remote..."><Widget message="Hello from Host!" /></Suspense>
      ) : (
        <p>Initializing...</p>
      )}
    </div>
  );
}
```

```javascript
// host/src/index.jsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
```

---

## 7\. Running the Example

1. **Start the remote** on port 4000:
    
    ```powershell
    cd remote
    npm run start  # serves remoteEntry.js and HTML
    ```
    
2. **Start the host** on port 3000:
    
    ```powershell
    cd host
    npm run start
    ```
    
3. Open [http://localhost:3000](http://localhost:3000/). You should see the Host heading and the remote widget below it.
    

---

## 8\. Pros & Cons vs. Standard Webpack MF

| Aspect | MF 1.x (Webpack) | MF V2 (Enhanced) |
| --- | --- | --- |
| **Runtime API** | Manual `__webpack_init_sharing__` + `container.init` | Clean `init()` + `loadRemote()` |
| **Share Scope Management** | Each host has its own, can collide | Single global share scope, idempotent inits |
| **Dynamic URLs** | Must bake remotes at build time | Can register or override remotes at runtime easily |
| **Multiple Hosts** | Risk of "already initialized" errors | No conflicts: caches and reuses remotes |
| **Bundle Size** | Larger initial host if `eager` share used | Minimal, can load remotes only when needed |
| **Flexibility** | Statically defined remotes in `webpack.config.js` | Dynamic registration, easier multi-env overrides |
| **Learning Curve** | Lower (built into Webpack) | Slightly higher (install enhanced runtime) |

---

## 9\. Conclusion

Module Federation V2’s enhanced runtime simplifies dynamic micro‑frontend loading in React apps. By centralising share‑scope management and offering straightforward APIs, it avoids the pitfalls of classic MF—especially in multi‑host or multi‑remote scenarios. While it adds a small dependency and learning step, the benefits of runtime flexibility, conflict‑free share scopes, and leaner bundles make it a compelling upgrade path.

Happy micro‑frontending! 🚀
