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

Booking module preview

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: Main container app with booking module's componet

Source code can be found here

Next steps - Deployment coming soon

Did you find this article valuable?

Support Ramesh Vishnoi by becoming a sponsor. Any amount is appreciated!