DEV Community

Sam Ho
Sam Ho

Posted on

Micro Frontend with Module Federation in React

This article is originally published on here.

This article will give you an overview of using micro frontend architecture and module federation to build scalable and efficient web applications, with a step-by-step guide to building a micro frontend using module federation in React.

What is Micro frontend?

Micro frontend is an architectural pattern for building web applications that involves breaking down a large frontend monolith into smaller, more manageable pieces. Each piece is an independent application, or microfrontend, that can be developed, tested, and deployed separately. This allows teams to work more efficiently and independently, as well as improve the performance and scalability of the overall application.

What is Module Federation?

Module Federation is a way to share code between different applications or micro-frontends. It allows you to distribute your code across multiple domains, which is essential when working with large-scale applications.

How Does it Work?

Module Federation is a system that allows you to use parts of an application in another application without having to import the entire application. This means that you can break down your application into smaller modules, making it easier to manage and update.

Image description

With module federation, you can share code between multiple applications.

Benefits of Module Federation

Module Federation has several benefits, including:

  • Scalability: With Module Federation, you can easily add new features to your application without having to rewrite your entire application.

  • Efficiency: By breaking down your application into smaller modules, you can make your application more efficient, reducing the time it takes to load and run.

  • Collaboration: Module Federation allows developers to work together more easily, as it makes it easier to share code between applications.

Example

Here is a demo to build a micro frontend using Module Federation. We are going to build two applications: Home App and Header App.

Full source code

Image description

You can follow these steps:

Step 1 — Create Applications

Create a new React application using create-react-app or your preferred method. In your root directory, create two applications:

npx create-react-app home-app

npx create-react-app header-app
Enter fullscreen mode Exit fullscreen mode

Step 2 — Install Webpack 5

Within each application, install webpack 5 and related dependencies.

#NPM
npm install -–save-dev webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader css-loader

#Yarn
yarn add -D webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader css-loader
Enter fullscreen mode Exit fullscreen mode

Step 3 — Customize Home Page

Image description

Customize your Home Page. For example, In home-app/App.js:

import React from 'react'; // Must be imported for webpack to work
import './App.css';

function App() {
  return (
    <div className="App">
      <div className="container">This is my home page</div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Run your home-app:

cd ./home-app && yarn && yarn start
Enter fullscreen mode Exit fullscreen mode

Step 4— Customize Header Page

Image description

Customize your Header Page. For example, In header-app/App.js:

import React from 'react'; // Must be imported for webpack to work
import './App.css';

function App() {
  return (
    <div className="HeaderApp">
      <div>Header</div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Run your header-app:

cd ./home-app && yarn && yarn start
Enter fullscreen mode Exit fullscreen mode

Step 5— Webpack Configuration

Create webpack.config.js file at the root of header-app/ and home-app/:

//home-app/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    entry: "./src/index",
    mode: "development",
    devServer: {
        port: 3000,  // port 3001 for header-app
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)?$/,
                exclude: /node_modules/,
                use: [
                    {
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/preset-env", "@babel/preset-react"],
                    },
                    },
                ],
            },
            {
                test: /\.css$/i,
                use: ["style-loader", "css-loader"],
            },
        ],
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: "./public/index.html",
        }),
    ],
    resolve: {
        extensions: [".js", ".jsx"],
    },
    target: "web",
};
Enter fullscreen mode Exit fullscreen mode

Modify header-app/src/index.js & home-app/src/index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('app'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Modify header-app/public/index.html & home-app/public/index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React with Webpack</title>
  </head>
  <body>
    <div id="app"></div>

    <script src="main.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Change the start script in header-app/package.json & home-app/package.json to utilize our webpack config:

"scripts": {
    "start": "webpack serve",
    "build": "webpack --mode production",
 },
Enter fullscreen mode Exit fullscreen mode

Run your header-app and home-app:

cd home-app && yarn start
cd header-app && yarn start
Enter fullscreen mode Exit fullscreen mode

Step 6— Module Federation Configuration

First, we need add a file entry.js as an entry to each of our app.

We need this additional layer of indirection because it allows Webpack to load all necessary imports for rendering the remote app.

Create header-app/entry.js and home-app/entry.js:

import('./index.js')
Enter fullscreen mode Exit fullscreen mode

Modify the entry of header-app/webpack.config.js and home-app/webpack.config.js

module.exports = {
    entry: "./src/entry.js",
    //...
}
Enter fullscreen mode Exit fullscreen mode

Step 7— Exposes Header for Module Federation

Now we need to expose Header for home-app to use, in our header-app/webpack.config.js:

// header-app/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// import ModuleFederationPlugin from webpack
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin"); 
// import dependencies from package.json, which includes react and react-dom
const { dependencies } = require("./package.json");
Enter fullscreen mode Exit fullscreen mode
module.exports = {
    //...
    plugins: [
        //...
        new ModuleFederationPlugin({
            name: "HeaderApp",  // This application named 'HeaderApp'
            filename: "remoteEntry.js",  // output a js file
            exposes: { // which exposes
              "./Header": "./src/App",  // a module 'Header' from './src/App'
            },
            shared: {  // and shared
              ...dependencies,  // some other dependencies
              react: { // react
                singleton: true,
                requiredVersion: dependencies["react"],
              },
              "react-dom": { // react-dom
                singleton: true,
                requiredVersion: dependencies["react-dom"],
              },
            },
        }),
    ],
};
Enter fullscreen mode Exit fullscreen mode

Start the app, navigate to http://localhost:3001/remoteEntry.js. This is a manifest of all of the modules that are exposed by the header application.


Step 8— Add Module Federation in Home App

Now we need to add the ModuleFederationPlugin to home-app/webpack.config.js:

// home-app/webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
// import ModuleFederationPlugin from webpack
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
// import dependencies from package.json, which includes react and react-dom
const { dependencies } = require("./package.json");

module.exports = {
    //...
    plugins: [
        //...
        new ModuleFederationPlugin({
            name: "HomeApp",  // This application named 'HomeApp'
            // This is where we define the federated modules that we want to consume in this app. 
            // Note that we specify "Header" as the internal name 
            // so that we can load the components using import("Header/"). 
            // We also define the location where the remote's module definition is hosted: 
            // Header@[http://localhost:3001/remoteEntry.js]. 
            // This URL provides three important pieces of information: the module's name is "Header", it is hosted on "localhost:3001", 
            // and its module definition is "remoteEntry.js".
            remotes: { 
                "HeaderApp": "HeaderApp@http://localhost:3001/remoteEntry.js",            
            },
            shared: {  // and shared
                ...dependencies,  // other dependencies
                react: { // react
                    singleton: true,
                    requiredVersion: dependencies["react"],
                },
                "react-dom": { // react-dom
                    singleton: true,
                    requiredVersion: dependencies["react-dom"],
                },
            },
        }),
    ],
};
Enter fullscreen mode Exit fullscreen mode

Modify home-app/src/App.js to use the Header component from the remote app:

import React, { lazy, Suspense } from 'react'; // Must be imported for webpack to work
import './App.css';

const Header = lazy(() => import('HeaderApp/Header'));

function App() {
  return (
    <div className="App">
      <Suspense fallback={<div>Loading Header...</div>}>
        <Header />
      </Suspense>
      <div className="container">Demo home page</div>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Run your home-app and header-app:

cd home-app && yarn start
cd header-app && yarn start
Enter fullscreen mode Exit fullscreen mode

Done!

Navigate to Home App http://localhost:3000

And done! You have successfully created a module federated app with a header.

Image description


Conclusion

Micro frontend architecture and module federation are powerful tools for building scalable and efficient web applications. By breaking down a large monolithic frontend into smaller, more manageable pieces, teams can work more efficiently and independently. Module federation then allows developers to share code between applications, enabling collaboration and reducing duplication of effort. By following the steps outlined in this article, you can build your own micro frontend with module federation in React and take advantage of these powerful tools.


Useful Resources

The full source code is avaliable in my Github repository.

If you have any further questions, feel free to comment below or reach me directly on LinkedIn.

Top comments (0)