Module Federation lets you run code from one JavaScript application inside another, even if they were built and deployed independently.

Let’s see it in action. Imagine you have two React applications: Host and Remote. Host wants to use a Button component that Remote exposes.

Here’s a simplified webpack.config.js for the Remote app:

// remote/webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devServer: {
    port: 3001,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new ModuleFederationPlugin({
      name: 'remote', // The name of this remote application
      filename: 'remoteEntry.js', // The filename for the remote entry point
      exposes: {
        './Button': './src/Button', // Expose the Button component under the alias './Button'
      },
      shared: {
        react: { singleton: true, eager: true }, // Share React
        'react-dom': { singleton: true, eager: true }, // Share React DOM
      },
    }),
  ],
};

And here’s the webpack.config.js for the Host app:

// host/webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devServer: {
    port: 3000,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        remote: 'remote@http://localhost:3001/remoteEntry.js', // Tells the host where to find the remote app
      },
      shared: {
        react: { singleton: true, eager: true },
        'react-dom': { singleton: true, eager: true },
      },
    }),
  ],
};

Now, in Host’s src/App.js, you can dynamically import the Button from Remote:

// host/src/App.js
import React, { Suspense, lazy } from 'react';

const Button = lazy(() => import('remote/Button')); // Import from the remote app

function App() {
  return (
    <div>
      <h1>Host App</h1>
      <Suspense fallback={<div>Loading Button...</div>}>
        <Button />
      </Suspense>
    </div>
  );
}

export default App;

When Host runs, it fetches remoteEntry.js from http://localhost:3001. This remoteEntry.js file acts as a manifest, telling Host what modules Remote exposes and how to load them. Host then uses this information to dynamically load Button from Remote when needed.

The core problem Module Federation solves is the "dependency hell" and code duplication that arises when multiple independent applications need to share common libraries or components. Traditionally, you’d either bundle everything into one massive application or manage shared dependencies through separate npm packages, which can lead to version conflicts and deployment headaches. Module Federation breaks down these barriers by allowing applications to dynamically load code from each other at runtime.

Internally, Webpack’s ModuleFederationPlugin transforms your application into a set of "remotes" (applications that expose modules) and "hosts" (applications that consume modules from remotes). The name in ModuleFederationPlugin identifies your application. exposes maps an alias (like './Button') to a local module path ('./src/Button'). remotes in the host application defines the URL where the remote’s remoteEntry.js can be found, aliased by the remote’s name. The shared option is crucial; it specifies dependencies that should be shared between the host and remote applications. Using singleton: true ensures that only one version of a shared dependency is loaded across all federated applications, preventing multiple instances of React, for example. eager: true will load the shared module as soon as the application starts, rather than waiting for it to be explicitly imported.

The magic happens when import('remote/Button') is encountered. Webpack, aware of the remotes configuration, knows to look for remoteEntry.js at http://localhost:3001. It then parses remoteEntry.js to find the actual location of the Button module and loads it dynamically. This is all managed by Webpack’s runtime code, which orchestrates the loading and linking of modules between applications.

A common point of confusion is how shared dependencies are managed. If your Host app depends on React v18 and your Remote app depends on React v17, and both declare react as shared, the singleton: true option will enforce that only one version is loaded. By default, the version requested by the first application to load the shared module will be used. If the other application needs a different version, it might fail. To avoid this, it’s best practice to ensure all federated applications agree on major versions of shared dependencies or to use version negotiation strategies within the shared configuration.

The next challenge you’ll likely encounter is managing routing and state across federated applications.

Want structured learning?

Take the full Webpack course →