Building Micro Frontend with React & Module Federation
Tags: react, microservices, webpack, tailwindcss
Published: 2022-04-30 09:18:24

We will be creating a production-ready micro-front end app using React, Redux, Typescript, Tailwind CSS, React Router, and Webpack but the scope of the article will be too broad and will be split up into a series

Here in this unit, we will be setting up a simple micro front end app with React, Typescript, and Tailwind CSS, and yes to simplify things, we will be using Lerna to set up a mono repo.

## What's a Micro FrontEnd?
Micro frontends are similar to the microservices concept. Here each part of the WebApp you see, for instance, the Header can be in react and the sidebar can be in Angular, Vue, or any other framework. So we will have a Host/Container app that will fetch bundled codes from different URLs on load. It opens up the possibility of independent teamwork without any interdependency.

Without boring you much with the details, let's get started and will tell you the details later.

## Folder Formation
Create the folders in a similar way.
```
- micro-frontend
   - packages
      - header
      - host
```

Yes, we will be just having a header as a micro front end for now, and the host will be calling the header on load. If you are wondering why we have created these folders under packages, it's because we are using Lerna, and it's the recommended practice.

## Header

Lets initialize npm in the folder.

```
npm init
```
Now install the main dependencies.

```
npm i react react-dom
```
Module federation is still not yet implemented in Create-React-App(CRA). So will be using webpack 5 to build the project. In CRA, under the hood, It's using Webpack but with CRA, we are completely freed up from the hustle of setting up webpack. It's not that complex to set it up if we understand what it's doing.

let's install the dev-dependencies.

```
npm i -D @babel/core @babel/preset-react @babel/preset-typescript autoprefixer babel-loader css-loader file-loader html-webpack-plugin mini-css-extract-plugin postcss postcss-loader style-loader tailwindcss webpack webpack-cli webpack-dev-server
```
As we are using typescript to write this project, let's install the required type definitions.

```
npm i -D @types/mini-css-extract-plugin @types/react @types/react-dom
```
Now, your package.json would look like the below.

_package.json_
```
{
  "name": "header",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.17.9",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    "@types/mini-css-extract-plugin": "^2.5.1",
    "@types/react": "^18.0.5",
    "@types/react-dom": "^18.0.1",
    "autoprefixer": "^10.4.4",
    "babel-loader": "^8.2.5",
    "css-loader": "^6.7.1",
    "file-loader": "^6.2.0",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.6.0",
    "postcss": "^8.4.12",
    "postcss-loader": "^6.2.1",
    "style-loader": "^3.3.1",
    "tailwindcss": "^3.0.24",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.8.1"
  }
}

```
As mentioned above, We are using webpack, to limit the scope of this article, we are not gonna go into many details but will just give you a high-level overview.

**What is webpack?**
Webpack is a module bundler library, Which means, that when we run an npm run/serve command against a webpack project, webpack will kick in and it will read through webpack.config.js then compile and build your project using the dependencies that we mentioned in this config file. In webpack we have plugins and modules, 

**Loaders** work at a file level, If we mention the file extension and the dependency then webpack will use that dependency to compile/transpile the files with mentioned extensions.

**Plugins** work at a system level. They can work on the pattern, file system handling (name, path), etc. For instance, we are using CleanWebpackPlugin, which will clean the bundle folder before generating another build.

HtmlWebpackPlugin: It will generate an HTML5 file for you that includes all your webpack bundles in the body using script tags.

MiniCssExtractPlugin: It extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.

ModuleFederationPlugin: Module federation allows a JavaScript application to dynamically run code from another bundle/build, on the client and server. And here below, we expose header component.

and now that you know what is webpack, let's create the config file.

_webpack.config.js_
```
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require("./package.json").dependencies;

module.exports = {
    entry: './src/index.ts',
    output: {
        filename: '[name].[contenthash].js',
        path: path.join(process.cwd(), 'dist')
    },
    plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
        }),
        new HtmlWebpackPlugin({
            template: './public/index.html',
        }),
        new ModuleFederationPlugin({
            name: 'header',
            filename: 'remoteEntry.js',
            exposes: {
                './header': './src/Header',
            },
            shared: {
                ...deps,
                react: {
                    singleton: true,
                    requiredVersion: deps.react,
                },
                "react-dom": {
                    singleton: true,
                    requiredVersion: deps["react-dom"],
                },
            },
        })
    ],
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: ["@babel/preset-typescript", "@babel/preset-react"]
                    }
                }],
                exclude: /[\\/]node_modules[\\/]/
            },
            {
                test: /\.(css|s[ac]ss)$/i,
                use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
            },
            {
                test: /\.(png|jpg|gif)$/i,
                type: 'asset/resource'
            }
        ]
    }
}
```
Let's create the react files.

**index file**, let's just import the Bootstrap file where we exactly do the stuff that usually gets done in the index file. Its because you might get run into an error like _Shared module is not available for eager consumption_

_index.ts_
```
import('./Bootstrap')
```

_bootstrap.tsx_
```
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Header from './Header';

ReactDOM.render(<Header />, document.getElementById('root'));
```

and one importing thing here for you to notice, We are exposing the header component through module federation and you should be importing necessary CSS in the header component, So the CSS you imported will be exposed for the exposed component and its subcomponents. Parent component CSS wont be exposed.

_header.tsx_
```
import * as React from 'react';
import "./header.scss"

const Header = () => {
  return (
    <nav class="font-sans flex flex-col text-center sm:flex-row sm:text-left sm:justify-between py-4 px-6 bg-white shadow sm:items-baseline w-full">
      <div class="mb-2 sm:mb-0">
        <a href="/home" class="text-2xl no-underline text-grey-darkest hover:text-blue-dark">Simple Header</a>
      </div>
      <div>
        <a href="/one" class="text-lg no-underline text-grey-darkest hover:text-blue-dark ml-4">Link 1</a>
        <a href="/two" class="text-lg no-underline text-grey-darkest hover:text-blue-dark ml-4">Link 2</a>
        <a href="/three" class="text-lg no-underline text-grey-darkest hover:text-blue-dark ml-4">Link 3</a>
      </div>
    </nav>
  )
};
export default Header
```

_header.css_
```
@tailwind base;
@tailwind components;
@tailwind utilities;
```

that's it, now if you run npm serve in this folder, it will just start running on port 3001

## Host

Let's create the host app and call the header app into it.

let's initiate npm

```
 npm init  
```

and the main dependencies
```
npm i react react-dom  
```

and now the dev-dependencies. if you notice, here we are not installing some libraries like Tailwind CSS, which is not necessary.
```
npm i -D @babel/core @babel/preset-react @babel/preset-typescript babel-loader css-loader html-webpack-plugin mini-css-extract-plugin postcss postcss-loader style-loader webpack webpack-cli webpack-dev-server clean-webpack-plugin
```

now your package.json file might look similar below, don't miss out to add the script section to yours. It's needed for running the app.

_package.json_
```
{
  "name": "host",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "serve": "webpack serve --mode development --port 3000  --open",
    "build-dev": "webpack --mode development",
    "build-prod": "webpack --mode production"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.17.9",
    "@babel/preset-react": "^7.16.7",
    "@babel/preset-typescript": "^7.16.7",
    "babel-loader": "^8.2.5",
    "css-loader": "^6.7.1",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.6.0",
    "postcss": "^8.4.12",
    "postcss-loader": "^6.2.1",
    "style-loader": "^3.3.1",
    "webpack": "^5.72.0",
    "webpack-cli": "^4.9.2",
    "webpack-dev-server": "^4.8.1"
  }
}

```

And here below we consume the header component with the module federation plugin.

_webpack.config.js_
```
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
    entry: './src/index.ts',
    output: {
        filename: '[name].[contenthash].js',
        path: path.join(process.cwd(), 'dist')
    },
    plugins: [
        new CleanWebpackPlugin(),
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
        }),
        new HtmlWebpackPlugin({
            template: './public/index.html',
        }),
        new ModuleFederationPlugin({
            remotes: {
                header: 'header@http://localhost:3001/remoteEntry.js',
            }
        })
    ],
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)?$/,
                use: [{
                    loader: 'babel-loader',
                    options: {
                        presets: ["@babel/preset-typescript", "@babel/preset-react"]
                    }
                }],
                exclude: /[\\/]node_modules[\\/]/
            }
        ]
    }
}
```

and lets create the react files

_index.ts_
```
import('./Bootstrap')
```

_bootstrap.tsx_
```
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import Host from './host';

ReactDOM.render(<Host />, document.getElementById('root'));
```

Here we are importing the header and wrapping it in react suspense because we are lazy loading the header and it will show an indicator till all the children load.

_host.tsx_
```
import * as React from 'react';
const Header = React.lazy(() => import('header/header'));

const Host = () => (
    <>
        <React.Suspense fallback="Loading...">
            <div>
                <Header />
            </div>
        </React.Suspense>
    </>
);

export default Host;
```
And here, we need the type definition for the header because the actual header is in another project which we are fetching via URL.

_types.d.ts_
```
declare module 'header/header' {
    export default Object
}
```
now at this point, if you run npm serve in the host folder, it will just start running and would suggest you run header app before starting this, or else it would be just blank

## Monorepo - Setting up lerna

Setting up Lerna is just an optional step, which has nothing to do with micro front-end architecture. Mono-repo just helps us to run/serve all projects at once without going into each folder in our local system. So you can skip this section if you don't want to include everything in a single repo.

copy the below file to your root folder(outside of your package folder) and run an npm install.

_package.json_
```
{
  "name": "root",
  "private": true,
  "scripts": {
    "serve": "lerna run --parallel serve",
    "kill-ports": "kill-port --port 3000,3001,3002,3003,3004,3005,3006"
  },
  "devDependencies": {
    "kill-port": "^1.6.1",
    "lerna": "^4.0.0"
  }
}
```
and create the lerna config file.

_lerna.json_
```
{
    "packages": [
        "packages/*"
    ],
    "version": "0.0.0"
}
```
That's enough! Now, if you run an npm serve in the root folder Lerna will start launching each app in parallel.

GitHub Repo: https://github.com/blessonabraham/micro-frontend-react