Beginner’s Guide to Module Federation
Module Federation is an advanced feature in Webpack that provides a way for a JavaScript application to dynamically load code from another application. This feature allows for efficient code sharing and dependency management.
It allows you to build Micro-Frontends (MFEs). A micro-frontend is a development approach that breaks down a web application’s front end into smaller, self-contained modules. Each module can be developed, tested, and deployed independently.
This allows you to integrate multiple smaller applications into a single application (called the host app or the shell app). The modules are loaded dynamically on demand.

Let’s get the basics right
Module Federation has three main components:
- Exposed Module (Remote App)
- Host Module (Shell App)
- Shared dependencies
Example:
Remote application exposes a component with react as shared dependency:
// Webpack file content of the remote app
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.jsx',
},
shared: {
'react': {
version: '18.2.0'
}
}
})
Host application receives the remote app:
// Webpack file content of the host app
new ModuleFederationPlugin({
name: 'host',
remotes: {
'remoteApp': 'remoteApp@http://localhost:3001/remoteEntry.js',
},
shared: ['react'],
})
Rendering the remote component in the host (dynamically):
// App.js in the host app
import React from 'react';
import RemoteAppButton = React.lazy(async () =>
await import('remoteApp/Button'));
const App = () => {
return (
<React.Suspense fallback="Loading">
<RemoteAppButton /> // You can pass props like a normal component
</React.Suspense>
);
}
Sharing Libraries
With the shared
key in the configuration you can define libraries that are shared between your federated modules. The package name is the same as the one found in the dependencies
section of the package.json.
Dependencies can be shared from both the host application and the remote application in a module federation setup, depending on how you configure it.
Host Application Sharing Dependencies: These shared dependencies will be included in the host’s bundle, and when a remote application requests them, they will be served from the host’s bundle. This allows the host application to control which dependencies are shared across the entire application.
Remote Application Sharing Dependencies: These dependencies are shared with the host application and other remote applications. These shared dependencies will be included in the remote’s bundle, and when the host or other remotes request them, they will be served from the remote’s bundle.
Why do we want to share libraries?
When you have federated modules, they’re bundled separately and include all the dependencies they need to function. However, when they’re used in a host application, it’s possible for multiple copies of the same dependency to be downloaded. This can hurt performance and make users download more JavaScript than necessary. 😰
This is where the shared API becomes really handy: You can avoid downloading multiple copies of the same dependency and improve the performance of your application. 🚀
Example
Let’s say you have two modules, Module A and Module B, and both of them require lodash
in order to function independently.
But when they’re used in a host application that brings both modules together, the Shared API comes into play. If there is already a preloaded, shared copy of lodash
available, Module A and Module B will use that copy instead of loading their own independent copies.
This copy could be loaded by made available by the host or another remote application inside it.
new ModuleFederationPlugin({
...
shared: ["lodash"],
});

API Definition
shared (object | [string])
: An object or Array of strings containing a list of dependencies that can be shared and consumed by other federated apps.
eager (boolean)
: If true, the dependency will be eagerly loaded and made available to other federated apps as soon as the host application starts. If false, the dependency will be lazily loaded when it is first requested by a federated app.
singleton (boolean)
: If true, the dependency will be treated as a singleton and only a single instance of it will be shared among all federated apps.
requiredVersion (string)
: Specifies the required version of the dependency. If a federated app tries to load an incompatible version of the dependency, two copies will be loaded.
shared: {
react: {
singleton: true,
requiredVersion: "^18.0.0",
},
"react-dom": {
singleton: true,
requiredVersion: "^18.0.0"
},
lodash: {
eager: true,
},
},
Versioning
So what happens if two remote modules use different versions of the same dependency?
Nothing, everything works! If the semantic version ranges for those dependencies don’t match, then Module Federation is powerful enough to identify them and provide separate copies. It’s just that module federation tries to download all the different versions, and overall bundle size increases.
Singleton Loading
Passing singleton: true
to the dependency object, ensures only that specific version of the dependency is downloaded.
Common Errors
When a module is required, it will load a file called remoteEntry.js
listing all the dependencies the module needs. Because this operation is asynchronous, the container can check all the remoteEntry
files and list all the dependencies that each module has declared in shared
; then, the host can load a single copy and share it with all the modules that need it.
Because shared
relies on an asynchronous operation to be able to inspect and resolve the dependencies, if your application or module loads synchronously and it declares a dependency in shared
, you might encounter the following error:
Uncaught Error: Shared module is not available for eager consumption
To solve the error above, there are two options:
Eager Consumption
Individual dependencies can be marked as eager: true
; this option doesn’t put the dependencies in an async chunk, so they can be provided synchronously; however, this means that those dependencies will always be downloaded, potentially impacting bundle size. The recommended solution is to load your module asynchronously by wrapping it into an async boundary:
Using Async Boundary
To create an async boundary, use a dynamic import to ensure your entry point runs asynchronously:
// index.js
import('./bootstrap.js');
// bootstrap.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
Importance of publicPath
The public path in webpack configuration specifies the base URL for all assets within your application. It’s crucial for resolving asset paths correctly, especially in applications with dynamic loading or code splitting.
In applications that use dynamic imports or code splitting, the public path determines the base URL for dynamically loaded chunks. This is essential for ensuring that webpack can load these chunks correctly, regardless of where they’re requested from in the application.
Public Path 'auto'
:
When you set publicPath
to 'auto'
, webpack will try to determine the correct public path based on where your HTML file is hosted. It essentially infers the public path dynamically based on the URL of the HTML file that loads the webpack bundle.
In a module federation setup, when the host serves assets to remote applications, it will use the dynamically determined public path based on 'auto'
. This ensures that assets are loaded correctly relative to the base URL of the HTML file, regardless of the deployment environment.
output: {
publicPath: 'auto', // this is for Module Federation exposition
},
...
plugins[
new HtmlWebpackPlugin({
publicPath: '/', // this is for standalone mode
...
],
Let’s Get in Touch
You are most welcome to follow me here on Medium. In addition, feel free to check out:
- My portfolio
- My LinkedIn Profile: Let’s connect!
- My Twitter Profile
References
https://webpack.js.org/concepts/module-federation/
https://www.infoxicator.com/en/module-federation-shared-api
https://scriptedalchemy.medium.com/understanding-webpack-module-federation-a-deep-dive-efe5c55bf366