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> ); }
Top comments (0)