Understanding Module-Federation with React / Webpack

Aashish TiwariAashish Tiwari
4 min read

Multiple separate builds should form a single application. These separate builds act like containers and can expose and consume code between builds, creating a single, unified application.

Javascript Module Federation is a concept that allows developers to share code and resources across multiple JavaScript applications enhancing the micro-frontend architecture.

In this blog we will create three web-apps using ReactJs utilising the Webpack (bundler) module-federation plugin.
Some of the terms to keep in mind for MF plugin are :-

  1. Host -> This is the webpack build which is consuming other remote bundles. This gets initialised first during the page load. In our case Main app is the Host.

  2. Remote -> This is the Webpack build which exposes some part of the application to be consumed by the host. In our case lib app is the remote.

  3. Bi-directional Host -> A host can be bidirectional as well mean it can consume other builds and can expose some part of the host build. In our case Component App is the Bi-directional Host.

Creating the Lib app.

npm init -y
npm i react react-dom @module-federation/enhanced
npm i -D webpack webpack-cli

create webpack.config.js file at the root with the following content.

const { ModuleFederationPlugin } = require("@module-federation/enhanced");
const path = require("path");
module.exports = {
  entry: "./index.js",
  mode: "development",
  devtool: "hidden-source-map",
  output: {
    publicPath: "http://localhost:3000/",
    clean: true,
  },
  module: {},
  cache: false,
  plugins: [
    new ModuleFederationPlugin({
      name: "lib_app",
      filename: "remoteEntry.js",
      exposes: {
        "./react": "react",
        "./react-dom": "react-dom",
      },
    }),
  ],
};

Here inside the plugins section we are using the ModuleFederation plugin which is exposing the react and react-dom dependencies.
After that run you web application and host the build locally using serve.

Creating the Component-app

use the same step mentioned in Lib-app to create Component-app and then add some additional dependencies.

npm i -D @babel/preset-react babel-loader css-loader style-loader url-loader
npm i html-webpack-plugin

Create a basic react app which has some components like Button, Dialog etc. Now create webpack.config.js with the following config.

const { ModuleFederationPlugin } = require("@module-federation/enhanced");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  entry: "./index.js",
  mode: "development",
  devtool: "hidden-source-map",
  output: {
    publicPath: "http://localhost:3001/",
    clean: true,
  },
  cache: false,
  resolve: {
    extensions: [
      ".jsx",
      ".js",
      ".json",
      ".css",
      ".scss",
      ".jpg",
      "jpeg",
      "png",
    ],
  },
  module: {
    rules: [
      {
        test: /\.(jpg|png|gif|jpeg)$/,
        loader: "url-loader",
      },
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.jsx?$/,
        loader: "babel-loader",
        exclude: /node_modules/,
        options: {
          presets: ["@babel/preset-react"],
        },
      },
    ],
  },
  plugins: [
    new ModuleFederationPlugin({
      name: "component_app",
      filename: "remoteEntry.js",
      exposes: {
        "./Button": "./src/Button.jsx",
        "./Dialog": "./src/Dialog.jsx",
      },
      remotes: {
        "lib-app": "lib_app@http://localhost:3000/remoteEntry.js",
      },
    }),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
    }),
  ],
};

Here, in the ModuleFederationPlugin Section we can see that we are exposing Button and Dialog component using exposes key that will be utilised in the main-app.
Here with the Remote key we are using the packages (react, react-dom) exposed by the lib-app.

Now we will create the main-app which will utilise the above two exposed remote containers.

create the webpack.config.js for the main-app which will contain both the remotes defined above.

// same as above file only with two new remotes.
 new ModuleFederationPlugin({
      name: "main_app",
      remotes: {
        "lib-app": "lib_app@http://localhost:3000/remoteEntry.js",
        "component-app": "component_app@http://localhost:3001/remoteEntry.js",
      },
    })

We can consume the data exposed by remotes in the following way.

import React from "lib-app/react";
import Button from "component-app/Button";
import Dialog from "component-app/Dialog";
export default function App () { 
  const [dialogVisible, setDialogVisible] = React.useState(false);
  // add handlers here
  return (
    <div>      
      <h4>Buttons:</h4>
      <Button type="primary" />
      <Button type="warning" />
      <h4>Dialog:</h4>
      <button onClick={handleClick}>click me to open Dialog</button>
      <Dialog
        switchVisible={handleSwitchVisible}
        visible={dialogVisible}
      />       
    </div>
  );
}

Run all the three apps together in their respective ports. ie,
lib-app : - port 3000
component-app :- port 3001
main-app: - port 3002

We can see that we are able to use the exposed components and libs in our main-app.

We can see in dev-tools that we are pointing to different container urls for consuming the component and libraries in our main-app. With the current configuration the components and the lib are loaded in parallel we can modify it based on our requirement. The main advantage is now we are able to do the changes in the run time and deploy the modules separately without building the whole application. It will also help in reducing the deployment time reducing the overall CI/CD cost and much more.....

You can find the complete source code on Github.

๐Ÿ‘‹ Hope you all have liked the article and if you have any queries feel free to reach out to me on LinkedinXGithub.

Reference :-
1. https://webpack.js.org/concepts/module-federation/
2. https://github.com/module-federation/module-federation-examples

1
Subscribe to my newsletter

Read articles from Aashish Tiwari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aashish Tiwari
Aashish Tiwari