DEV Community

Rahul Sharma
Rahul Sharma

Posted on • Updated on

The Complete Guide to Micro Frontend with React.js for 2022

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

What is a micro frontend?

The Micro Frontend style of architecture design does for the frontend of an application what microservices do for the backend, breaking monolithic structures into smaller components that can then be assembled on a single page.

Watch the video

Pros and cons of micro frontend:

Pros:

  • A micro frontend is more modular and reusable.
  • A micro frontend is more scalable.
  • The micro frontend is more maintainable.
  • Independent and Faster development.
  • Testing separate applications are easy.
  • Different front-end technologies can be used for different projects(like React, Angular, Vue.js, etc).

Cons:

  • Testing the entire application is not easy.
  • Sharing code, state(data), etc is not easy.

Module federation (Module Federation | webpack)

Module Federation allows a JavaScript application to dynamically load code from another application and in the process, share dependencies. If an application consuming a federated module does not have a dependency needed by the federated code, Webpack will download the missing dependency from that federated build origin.

Prerequisite:

  • Make sure you have installed the latest Node.js
  • Basic knowledge of React.js and Webpack 5+.
  • Code editor

I'll create 2 apps in this article:

  • First: Container app that will be used as a base for the micro frontends.
  • Second: The counter app that will get rendered inside the container app.

Let’s start with setting up the environment. I’m using create-mf-app to create a react app.

Container App

Installation

Run the following npx command on a terminal to install and bootstrap the application using “create-mf-app”. Let’s call our application “container”

npx create-mf-app
Enter fullscreen mode Exit fullscreen mode

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)
After completing the first step, The directory initially looks something like this:

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

I will not deep dive into the folder structure, It’s similar to the create-react-app folder structure.

NOTE: Only difference here is the index.js file, Which loads the app dynamically.

import('./App');
Enter fullscreen mode Exit fullscreen mode

Let’s quickly create another app called Counter using the same steps as above.

Counter App

npx create-mf-app
Enter fullscreen mode Exit fullscreen mode

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

Inside the counter app, I have created a Counter component in the components folder.

src/components/Counter.jsx

import React, { useState } from "react";
export const Counter = () => {
    const [count, setCount] = useState(0);
    const onIncrement = () => setCount(count + 1);
    const onDecrement = () => setCount(count - 1);
    return (
      <div>
        <h1>Counter App</h1>
        <p>Current count: <strong>{count}</strong></p>
        <button onClick={onIncrement}>+</button>
        <button onClick={onDecrement}>-</button>
      </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

Let’s update the webpack.config.js file inside the Counter app. Add ModuleFederationPlugin to the plugins array with the following configuration:

webpack.config.js

const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
  output: {
    publicPath: "http://localhost:8081/",
  },
  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
  },
  devServer: {
    port: 8081,
    historyApiFallback: true,
  },
  module: {
    rules: [
      {
        test: /\.m?js/,
        type: "javascript/auto",
        resolve: {
          fullySpecified: false,
        },
      },
      {
        test: /\.(css|s[ac]ss)$/i,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
  plugins: [ // This is important part
    new ModuleFederationPlugin({
      name: "counter",
      filename: "remoteEntry.js",
      remotes: {},
      exposes: {
        "./Counter": "./src/components/Counter",
      },
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

Let’s understand what each option is:

  1. name: Name of the remote app
  2. filename: Entry point(remoteEntry.js) for the counter app.
  3. remotes: Add remotes entry here (relevant for the container)
  4. exposes: All the component names that you want to expose to the container app.
  5. shared: container all the dependencies that you want to share between the container and the counter app.

Let’s update the webpack.config.js file inside the Container app.

webpack.config.js

const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
  output: {
    publicPath: "http://localhost:8080/",
  },
  resolve: {
    extensions: [".tsx", ".ts", ".jsx", ".js", ".json"],
  },
  devServer: {
    port: 8080,
    historyApiFallback: true,
  },
  module: {
    rules: [
      {
        test: /\.m?js/,
        type: "javascript/auto",
        resolve: {
          fullySpecified: false,
        },
      },
      {
        test: /\.(css|s[ac]ss)$/i,
        use: ["style-loader", "css-loader", "postcss-loader"],
      },
      {
        test: /\.(ts|tsx|js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
  plugins: [ // This is important part
    new ModuleFederationPlugin({
      name: "container",
      filename: "remoteEntry.js",
      remotes: {
        counter: "counter@http://localhost:8081/remoteEntry.js",
      },
      exposes: {},
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
        },
        "react-dom": {
          singleton: true,
          requiredVersion: deps["react-dom"],
        },
      },
    }),
    new HtmlWebPackPlugin({
      template: "./src/index.html",
    }),
  ],
};
Enter fullscreen mode Exit fullscreen mode

NOTE: The remote objects will have to define all the entry points exposed from remote apps, remotes entry has the following structure:

{ "app-name": "name@<remote-host>/remoteEntry.js" }
Enter fullscreen mode Exit fullscreen mode

src/App.jsx

import React from "react";
import ReactDOM from "react-dom";
import { Counter } from 'counter/Counter';
import "./index.css";
const App = () => (
  <div className="container">
    <h1>Container App</h1>
    <Counter /> // Micro frontend app
  </div>
);
ReactDOM.render(<App />, document.getElementById("app"));
Enter fullscreen mode Exit fullscreen mode

Time to run both the apps

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

Counter App

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

Container App

Micro Frontend with React.js and Module federation | Rahul Sharma(DevsMitra)

Github Repo:
https://github.com/devsmitra/micro

Reference:
https://github.com/jherr/create-mf-app

Got any questions or additional? please leave a comment.

Thank you for reading 😊

Must Read If you haven't

Catch me on

Youtube Github LinkedIn Medium Stackblitz Hashnode HackerNoon

Latest comments (10)

Collapse
 
almangor profile image
Alex • Edited

Out of curiosity:

  1. exposes: All the component names that you want to expose to the container app.

How to expose multiple components from the same "remote" app? And what if the "container" would be on Angular?

Collapse
 
devsmitra profile image
Rahul Sharma • Edited

// React app => inject.js

const toReactApp = (el) => ReactDOM.render(<Counter />, el);
export default toReactApp;
Enter fullscreen mode Exit fullscreen mode

// webpack.config.js

exposes: {
        "./Counter": "./src/components/Counter",
        "./Counter1": "./src/components/Counter1",
        './toReactApp': './src/inject.js',
}
Enter fullscreen mode Exit fullscreen mode

// Angular

import toReactApp from './<path>/toReactApp'

@Component({
    selector: 'app',
    template: ` <div #myDiv>Some text</div>`,
})
export class AppComponent implements AfterViewInit {
    @ViewChild('myDiv') myDiv: ElementRef;

    ngAfterViewInit() {
        toReactApp(this.myDiv.nativeElement);
    }
}
Enter fullscreen mode Exit fullscreen mode

I'm not angular expert, but I think you can use it like that.

Collapse
 
chema profile image
José María CL

Simple, Easy, Useful. Than you a lot!

Collapse
 
himedlooff profile image
Mike Morici

Any special setup if you were to use different versions of react for the host app vs the child app?

Collapse
 
devsmitra profile image
Rahul Sharma • Edited

No special setup required

Worst case: you can inject micro-frontend app to ref. This is valid solution when you want to use 2 different framework for microfrontend.

Collapse
 
tayejoseph profile image
Taye

Thank you very much for the article, was really helpful

Collapse
 
eggm4n profile image
Luke

Just testing this out and found it a lot easier than I expected, thank you this is a very neat article.

How would you address security concerns like seamlessly managing authentication & access control across multiple distributed apps?

How would you control/restrict who can add your export as a remote?

Collapse
 
devsmitra profile image
Rahul Sharma

content security policy you can use.

Collapse
 
shivampawar profile image
Shivam Pawar

Could you please confirm if we need react Lazy Loading to render micro frontend app inside container?

Collapse
 
devsmitra profile image
Rahul Sharma

Yes, we can do that.