DEV Community

Ramesh Vishnoi
Ramesh Vishnoi

Posted on

Micro-frontend Pratical guide - Part 2

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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'
        })
    ]
};
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

Multiple components can also be exposed just like

exposes: {
    "./Counter":"./src/Counter.js",
    "./Timer":"./src/Timer.js"
},
Enter fullscreen mode Exit fullscreen mode

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')
Enter fullscreen mode Exit fullscreen mode

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>
);
Enter fullscreen mode Exit fullscreen mode

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'
        })
    ]
}
Enter fullscreen mode Exit fullscreen mode

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",
              },
Enter fullscreen mode Exit fullscreen mode

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>
);

Enter fullscreen mode Exit fullscreen mode

modify index.js file as below

import('./bootstrap')
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

Output:
Main container app with booking module's componet

Source code can be found here

Next steps - Deployment coming soon

Top comments (0)