Photo by Joshua Reddekopp on Unsplash
Micro-frontend Pratical guide - Part 2
Follow up article on micro frontend architecture guide
In previous post, we discussed what is micro-frontend architecture and its pros and cons. In this post, we'll go through the steps required in setting up the project - how to initialize the main container app and a sample module using React.
We'll assume that our complex app needs some booking functionality. let's start with one module (booking-module) and a container(or master) app
Initialize Booking module with
npx create-react-app booking-module
cd booking-module && npm start
make sure everything is working as expected.
Create a Counter component which will be exposed to main container app.
// src/Counter.js
import React, { useState } from 'react'
const Counter = () => {
const [count, setCount] = useState(0);
const handleIncrement = () => setCount(count + 1);
const handleDecrement = () => setCount(count - 1);
return (
<div className='counter'>
<h3>This is Counter App Module</h3>
<p><strong>{count}</strong></p>
<div>
<button onClick={handleIncrement} className='counter-btn'>+</button>
<button onClick={handleDecrement} className='counter-btn'>-</button>
</div>
</div>
)
}
export default Counter;
Update your App.js
import './App.css';
import Counter from './Counter';
function App() {
return (
<div className="App">
<h3>This is Booking module</h3>
<Counter/>
</div>
);
}
export default App;
Next step: Introduce webpack
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin
Create webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
const deps = require('./package.json').dependencies
module.exports = {
mode: 'development',
devServer: {
port: 8082,
},
output: {
filename: 'main.js',
},
module: {
rules: [{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: { presets: [
'@babel/env',
["@babel/preset-react", {"runtime": "automatic"}]
] },
}, {
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}],
},
plugins: [
new ModuleFederationPlugin({
name: "bookingModule",
filename: "remoteEntry.js",
remotes: {},
exposes: {
"./Counter":"./src/Counter.js"
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
Here ModuleFederationPlugin
will expose your components as a separate bundle. Note - name field inside ModuleFederationPlugin
will be used while importing components, so make sure you have a unique module name.
We exposed Counter component like this
exposes: {
"./Counter":"./src/Counter.js"
}
Multiple components can also be exposed just like
exposes: {
"./Counter":"./src/Counter.js",
"./Timer":"./src/Timer.js"
},
Next Step -
npx webpack serve
- this will run your module instance on given port. 8082 in my case
Restructure your module's entry point file. By default, src/index.js is the entry file and it contains multiple import which creates problem while building bundle Modify src/index.js file as follow -
import('./bootstrap')
Create bootstrap.js file with following content -
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Initialize Main Container app
npx create-react-app container
cd container
Install Webpack dependencies
npm install webpack webpack-cli webpack-dev-server html-webpack-plugin
Create webpack.config.js file
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require("webpack").container.ModuleFederationPlugin;
const deps = require('./package.json').dependencies
module.exports = {
mode: 'development',
devServer: {
port: 8080,
},
output: {
filename: 'main.js',
},
module: {
rules: [{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: { presets: [
'@babel/env',
["@babel/preset-react", {"runtime": "automatic"}]
] },
}, {
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}],
},
plugins: [
new ModuleFederationPlugin({
name: "container",
remotes: {
bookingModule:"bookingModule@http://localhost:8082/remoteEntry.js",
},
exposes: {
},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
}
We are now telling webpack to import module from this url and multiple modules can be imported similar to how we did in booking module.
remotes: {
bookingModule:"bookingModule@http://localhost:8082/remoteEntry.js",
},
Now you are ready to consume booking-module's component, but before we need to change the entry point of the project similar to how we did in booking module.
create a bootstrap.js file with content
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
modify index.js file as below
import('./bootstrap')
Now import Counter from booking module and consume component
import Counter from 'bookingModule/Counter';
function App() {
return (
<div className="App">
<h3>This is Container App</h3>
<Counter/>
</div>
);
}
Output: