Micro-Frontends with Webpack 5 Module Federation

Photo by AltumCode on Unsplash

The pain of the “monster-lith”…

The World of Microservices

The Single SPA

Enter Module Federation

How does it work?

A real-life example

container (orchestrator)
about_page
design_system

The Rundown

src
App.js
routes.js
Navigation.js
Home.js
index.js
index.html
bootstrap.js
package.json
webpack.config.json
//others remitted for instructional purposes
const HtmlWebPackPlugin = require('html-webpack-plugin');
const path = require('path');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const htmlPlugin = new HtmlWebPackPlugin({
template: './src/index.html',
filename: './index.html'
});
module.exports = {
mode: 'development',
devServer: {
contentBase: path.join(__dirname, 'dist'),
port: 3000
},
output: {
publicPath: 'http://localhost:3000/'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new ModuleFederationPlugin({
name: 'federation_demo_container',
library: { type: 'var', name: 'federation_demo_container' },
filename: 'remoteEntry.js',
exposes: {
'./Home': './src/Home'
},
shared: ['react', 'react-dom']
}),
htmlPlugin
]
};
new ModuleFederationPlugin({
name: 'federation_demo_container',
library: { type: 'var', name: 'federation_demo_container' },
filename: 'remoteEntry.js',
exposes: {
'./Home': './src/Home'
},
shared: ['react', 'react-dom']
})
courtesy of giphy
//bootstrap.jsimport React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
import('./bootstrap');

Adding a route

//webpack.config.jsonnew ModuleFederationPlugin({
name: 'federation_demo_about',
library: { type: 'var', name: 'federation_demo_about' },
filename: 'remoteEntry.js',
remotes: {
federation_demo_container: 'federation_demo_container'
},
exposes: {
'./routes': './src/routes'
},
shared: ['react', 'react-dom']
})
//index.html<html>
<head>
<title>Federation Demo - About</title>
<script src="http://localhost:3000/remoteEntry.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
//routes.jsimport React from 'react';const About = React.lazy(() => import('./About'));const routes = [
{
path: '/about',
component: About
}
];
export default routes;
//webpack.config.jsonnew ModuleFederationPlugin({
name: 'federation_demo_container',
library: { type: 'var', name: 'federation_demo_container' },
filename: 'remoteEntry.js',
remotes: {
federation_demo_about: 'federation_demo_about'
},

exposes: {
'./Home': './src/Home'
},
shared: ['react', 'react-dom']
})
<script src="http://localhost:3002/remoteEntry.js"></script>
import React, { lazy, Suspense } from 'react';
import { HashRouter, Route, Switch } from 'react-router-dom';
import Navigation from './Navigation';
import localRoutes from './routes';
import remoteRoutes from 'federation_demo_about/routes';
const routes = [...localRoutes, ...remoteRoutes];const App = () => (
<HashRouter>
<div>
<Navigation />
<Suspense fallback={<div>Loading...</div>}>
<Switch>
{routes.map((route) => (
<Route
key={route.path}
path={route.path}
component={route.component}
exact={route.exact}
/>
))}
</Switch>
</Suspense>
</div>
</HashRouter>
);
export default App;
The Home page
The About page

Introducing a third package — the design system

new ModuleFederationPlugin({
name: 'federation_demo_design',
library: { type: 'var', name: 'federation_demo_design' },
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button'
},
shared: ['react', 'react-dom']
})
federation_demo_design: 'federation_demo_design'
<script src="http://localhost:3001/remoteEntry.js"></script>
//Home.jsimport React, { lazy, Suspense } from 'react';const Button = lazy(() => import('federation_demo_design/Button'));const Home = () => (
<div style={{ padding: '16px' }}>
<h1 style={{ color: '#264653' }}>Home Page</h1>
<Suspense fallback="Loading Button...">
<Button>Click me on home!</Button>
</Suspense>

</div>
);
export default Home;
Home Page
About page

Closing

About Brian