Unlock Seamless Code Sharing with Webpack 5 Module Federation
This article explains how Webpack 5's Module Federation feature enables dynamic code sharing and dependency sharing across micro‑frontend applications, walks through its core concepts, shows practical configuration and code examples, and discusses its advantages, drawbacks, and typical use cases.
For a while I have heard that the new Webpack 5 feature Module Federation can solve code‑sharing problems, but we have not used it in our team because existing projects are not on Webpack 5 and our micro‑frontend exploration already provided a good solution.
To explore the possibility of combining Module Federation with our micro‑frontend approach, we decided to study its underlying principles.
Concept
Module Federation
What is Module Federation (MF)? The Webpack documentation describes it as:
Multiple separate builds should form a single application. These separate builds should not have dependencies between each other, so they can be developed and deployed individually. This is often known as Micro‑Frontends, but is not limited to that.
In plain language, an application can be composed of several independent builds that have no direct dependencies and can be developed and deployed separately – the essence of micro‑frontends.
MF aims to split an application into multiple independent applications, each of which can be loaded dynamically and share dependencies.
Container
A bundle produced by ModuleFederationPlugin is called a Container . If an application is built with this plugin, it becomes a Container that can both load other Containers and be loaded by them.
Host & Remote
From the consumer and producer perspectives, a Container can be called a Host or a Remote :
Host : the consumer that dynamically loads and runs code from other Containers.
Remote : the provider that exposes attributes (components, functions, values) for the Host to use.
A Container can act as both Host and Remote at the same time.
Shared
A Container can share its dependencies (e.g., react, react-dom) with other Containers, enabling dependency sharing.
Usage Practice
Below is a simple example that demonstrates how to use MF.
Effect Demonstration
We have two applications, app1 and app2. app2 shares its Hello component with app1. Both apps share a single copy of react and react-dom.
Full source code can be downloaded from webpack5demo and run to view.
app1/src/app.js:
import React from 'react';
import App2Hello from 'app2/Hello';
const RootComponent = () => {
return (
<div>
<div>app1</div>
<App2Hello />
</div>
);
};
export default RootComponent; app1/src/bootstrap.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
ReactDOM.render(<App />, document.getElementById('app')); app1/src/index.js:
import('./bootstrap'); app2/src/Hello.js:
import React from 'react';
const Hello = () => {
return <div>app2 hello</div>;
};
export default Hello;Result:
When app1 imports the Hello component from app2, the remote module entry file ( app2RemoteEntry.js) is downloaded, the component code is fetched, and only the shared react and react-dom from app1 are used, achieving dynamic loading and dependency sharing.
How to Configure the Plugin?
Both applications use ModuleFederationPlugin: app2/webpack.config.js:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
// ...other config
plugins: [
new ModuleFederationPlugin({
name: 'app2',
filename: 'app2RemoteEntry.js',
exposes: {
'./Hello': './src/Hello',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
}; app1/webpack.config.js(as Host):
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
// ...other config
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'app1RemoteEntry.js',
remotes: {
app2: 'app2@http://127.0.0.1:8002/app2RemoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};Key fields:
name : alias of the current application, used as a prefix when other apps import from it.
filename : the remote entry file name that the Host will download.
exposes : defines which modules are exposed for remote consumption.
remotes : lists remote applications that this Host depends on.
shared : declares third‑party libraries that should be shared.
How It Works
Build Differences
Before using MF, the builds of app1 and app2 produce a single bundle. After enabling MF, additional chunks appear: remoteEntry‑chunk, shared‑chunk, expose‑chunk, and async‑chunk. These are generated by the plugin to handle remote entry, shared libraries, exposed modules, and asynchronous loading.
Loading Remote Modules
The runtime adds a function __webpack_require__.f.remotes that, when a chunk needs a remote module, first loads the remote entry file (e.g., app2RemoteEntry.js), then calls the remote’s get method to retrieve the requested module (e.g., ./Hello).
In the generated code you can see a mapping:
var chunkMapping = {
"src_bootstrap_tsx": ["webpack/container/remote/app2/Hello"]
};
var idToExternalAndNameMapping = {
"webpack/container/remote/app2/Hello": ["default", "./Hello", "webpack/container/reference/app2"]
};When the bootstrap chunk runs, it ensures the remote entry is loaded, then resolves the Hello component before executing the rest of the code.
Shared Dependencies
Webpack creates a shared scope object __webpack_require__.S that stores versions of shared libraries from all participating apps. Each app registers its versions via register. When a module needs a shared library, the runtime calls loadSingletonVersionCheckFallback (or loadStrictVersionCheckFallback if requiredVersion is set) to pick the appropriate version – usually the highest compatible one.
Example registration for react and react-dom:
register('react-dom', '16.14.0', () => import('vendors-node_modules__react-dom_16_14_0_react-dom_index_js'));
register('react', '16.14.0', () => import('vendors-node_modules__react_16_14_0_react_index_js'));If an app specifies requiredVersion, the runtime uses findValidVersion to locate that exact version; otherwise it picks the highest singleton version.
Application Scenarios
Code Sharing
Previously, sharing code between apps required copying files, publishing a shared npm package, or using a custom micro‑frontend loader. With MF, an app can simply expose a component, function, or value via exposes, and another app consumes it via remotes. Both synchronous and asynchronous imports are supported:
// Synchronous
import ServiceInfo from 'optimus/ServiceInfo';
// Asynchronous
const ServiceInfo = React.lazy(() => import('optimus/ServiceInfo'));This also enables on‑demand loading of UI libraries. For example, a component library tracks can expose its components:
new ModuleFederationPlugin({
name: 'tracks',
filename: 'tracksRemoteEntry.js',
exposes: {
'./PageHeader': './src/components/PageHeader',
'./Address': './src/components/Address',
'./Empty': './src/components/Empty',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});Consumers can then import synchronously or lazily:
import PageHeader from 'tracks/PageHeader';
// or
const PageHeader = React.lazy(() => import('tracks/PageHeader'));Common Dependencies
Shared libraries such as React, React‑DOM, or Ant Design can be treated as separate MF applications. Each app declares them as remotes, and the shared scope ensures that only one copy (or the appropriate version) is loaded at runtime.
Example: a react16 app exposing the React package:
// src/index.js
import * as React from 'react';
export default React;
// webpack.config.js
new ModuleFederationPlugin({
name: 'react16',
filename: 'react16RemoteEntry.js',
exposes: { './index': './src/index' },
});Other apps then import it as import React from 'react16/index'; and configure the remote path accordingly.
Pros
Framework‑agnostic solution for splitting large applications.
Enables seamless code and dependency sharing between multiple apps.
Supports multiple versions of the same dependency via shared scope.
Leverages the existing Webpack ecosystem, keeping learning and migration costs low.
Cons
Asynchronous loading of many remote entries can impact page performance.
Remote URLs must be managed manually, leading to version‑control challenges similar to npm packages.
Lack of type information for remote modules makes IDE support weaker.
No official tooling for simultaneous development of multiple MF apps.
Conclusion
Module Federation provides a powerful mechanism for dynamic module loading and automatic dependency management, offering a new way to think about application decomposition and code reuse. While it solves many long‑standing pain points, it also introduces challenges such as performance overhead, version management, and tooling gaps. The ecosystem is still evolving, and many of these issues can be addressed with custom tooling or future Webpack improvements.
Overall, MF is worth following and investing time in, especially when combined with a broader micro‑frontend architecture.
Follow the "Alibaba F2E" WeChat public account to stay updated on front‑end trends.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
