Webpack module federation: a new choice for micro front-end architecture
Webpack Module Federation is a revolutionary feature introduced in Webpack 5 that completely changes the way micro-frontend architecture is implemented. Module Federation allows different web applications (or micro-frontend applications) to dynamically share code at runtime without physical sharing in the traditional packaging or publishing process. This means that each micro-application can be independently developed, built, and deployed, while also being able to easily share components, libraries, and even business logic.

Basic Concepts
- Container application (Container): As the host of the micro-frontend architecture, responsible for loading and coordinating various micro-applications.
- Remote application (Remote): An independent micro-application that can expose its own modules to other applications for use, or consume modules from other applications.
Implementation steps
1. Container application configuration
In the webpack.config.js
of the container application, use ModuleFederationPlugin
to declare the source of the remote micro-application.
// webpack.config.js (Container)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...Other Configuration
plugins: [
new ModuleFederationPlugin({
name: 'container',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
app2: 'app2@http://localhost:3002/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
Here, the remotes
field specifies the name of the remote micro-app and its remote entry file URL. The shared
configuration indicates which modules should be shared as singletons, such as React and ReactDOM, to avoid duplicate loading.
2. Remote application configuration
In each remote application’s webpack.config.js
, ModuleFederationPlugin
is also used, but this time to expose its own modules.
// webpack.config.js (Remote App1)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...Other Configuration
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
exposes: {
'./MyComponent': './src/components/MyComponent',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
The exposes
field defines which modules will be exposed externally. In this example, the MyComponent component can be imported and used from the container application or other micro applications.
3. Consuming remote modules
In the container application or another remote application, you can directly import the remotely exposed modules.
// In a component of Container or another Remote App
import MyComponent from 'app1/MyComponent';
function App() {
return (
<div>
<h1>Container App</h1>
<MyComponent />
</div>
);
}
export default App;
Advantages
- Independent development and deployment: Each micro-application can be independently developed, built, and deployed, which improves development efficiency and deployment flexibility.
- On-demand loading: Only when a module is actually used, the corresponding remote code will be loaded, which optimizes the first screen loading time and overall performance.
- Version management and isolation: Each micro-application can freely upgrade its dependencies to avoid version conflicts.
- Easy to maintain and expand: The loose coupling feature of module federation makes it easy and quick to add or remove micro-applications.
Webpack module federation provides an efficient and flexible solution for the development and maintenance of modern Web applications by simplifying the code sharing mechanism in the micro-frontend architecture.
Actual combat case: Building a simple micro-frontend application
Let’s use a simple example to demonstrate how to use Webpack module federation to build two micro-applications: a container application and a remote application.
1. Create a container application
First, create a new React application as a container application:
npx create-react-app container-app
cd container-app
Install webpack and webpack-cli (note that since create-react-app already includes Webpack, it is usually not necessary to install it separately. This is only for demonstration purposes):
npm install webpack webpack-cli --save-dev
Modify package.json and add a startup script to configure Webpack:
"scripts": {
"start": "webpack serve --config webpack.config.js",
// ...
}
Create webpack.config.js
and configure Module Federation Plugin
:
// webpack.config.js (Container App)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...Other Configuration
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
new ModuleFederationPlugin({
name: 'containerApp',
remotes: {
remoteApp: 'remoteApp@http://localhost:3010/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
2. Create a remote application
Create a remote application in another directory:
npx create-react-app remote-app
cd remote-app
Similarly, modify package.json
, add the startup script, and install webpack
and webpack-cli
(for example only):
npm install webpack webpack-cli --save-dev
Configure the Module Federation Plugin in remote-app’s webpack.config.js to expose the component:
// webpack.config.js (Remote App)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
// ...Other Configuration
plugins: [
new ModuleFederationPlugin({
name: 'remoteApp',
filename: 'remoteEntry.js',
exposes: {
'./MyWidget': './src/MyWidget',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
],
};
Create the MyWidget.js
component in the remote-app/src
directory:
// MyWidget.js
import React from 'react';
const MyWidget = () => {
return <h1>Hello from Remote App!</h1>;
};
export default MyWidget;
3. Container application consumes remote components
Go back to container-app and import remote components where needed:
// container-app/src/App.js
import React from 'react';
import MyWidget from 'remoteApp/MyWidget';
function App() {
return (
<div className="App">
<header className="App-header">
<MyWidget />
</header>
</div>
);
}
export default App;
4. Start the application
Start two applications separately:
# In the remote application directory
npm start --port 3010
# In the container application directory
npm start
Now, when you access the container application in the browser, you should see that the components from the remote application are successfully loaded and displayed.
Advanced Usage and Best Practices
1. Dynamic loading and lazy loading
In a real project, you may want to dynamically load remote applications based on user behavior or specific conditions. Webpack module federation supports asynchronous loading, just use the import()
function when importing.
// container-app/src/App.js
import React, { lazy, Suspense } from 'react';
const MyWidget = lazy(() => import('remoteApp/MyWidget'));
function App() {
return (
<div className="App">
<Suspense fallback={<div>Loading...</div>}>
<MyWidget />
</Suspense>
</div>
);
}
export default App;
In this way, the MyWidget component will be loaded on demand when needed, improving the first screen loading speed.
2. Version management and dependency management
In the micro-frontend architecture, ensuring the compatibility of dependency versions between different applications is key. Using the shared
configuration of ModuleFederationPlugin
, you can specify the version range and loading strategy of the shared module (for example, singleton
, strictVersion
, etc.).
// webpack.config.js
new ModuleFederationPlugin({
// ...
shared: {
react: { version: '^17.0.0', singleton: true },
'react-dom': { version: '^17.0.0', singleton: true },
},
}),
3. Routing Integration
In the micro-frontend architecture, routing management is an important component. You can use libraries like react-router-dom
, combined with Microfrontends-Router
or custom solutions to implement cross-application routing jumps.
// container-app/src/Routes.js
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import App1 from './App1';
import App2 from './App2';
function Routes() {
return (
<Router>
<Switch>
<Route path="/app1" component={App1} />
<Route path="/app2" component={App2} />
</Switch>
</Router>
);
}
export default Routes;
4. State management
For shared state requirements, you can use state management libraries such as Redux, MobX, or Context API, or state management libraries designed specifically for micro-frontends such as single-spa-redux
, qiankun
's store
solution, etc.
5. Shared services and public libraries
In addition to components, you can also share services and public libraries. For example, create a dedicated remote application to provide API services, or share a public HTTP library.
// webpack.config.js (Remote App for Services)
new ModuleFederationPlugin({
name: 'services',
filename: 'remoteEntry.js',
exposes: {
'./ApiService': './src/services/ApiService',
'./HttpLibrary': './src/libs/http-library',
},
shared: {
// ...Other shared libraries
},
}),
6. Error handling and logging
In order to ensure the stable operation of the micro-frontend application, global error capture and logging need to be implemented. You can use window.onerror
, try...catch
statements, or use a dedicated logging library such as log4js.
// container-app/src/index.js
window.onerror = function (errorMessage, fileName, lineNumber, columnNumber, error) {
// Record error information
console.error(errorMessage, fileName, lineNumber, columnNumber, error);
// ...other processing logic
return true; // Prevent browser default error handling
};