Micro-frontend with Module Federations [Part 2] - Create React App
Tags: react, typescript, javascript, frontend
Published: 2023-06-21 23:12:37

Sup people!!! It's nice to have you here again!

And different of the state management journey, this is the last part of the articles, where I talk about module federation (perhaps... 👀)

![The office Kevin Smiling Gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c34nfdas9775cws3d7o6.gif)

In the previous article I talked about how to create a microfrontend using the federations module in **vite**, now let's talk how to create on Create React App (CRA).

So we will create the same application, but the config it will be different, because the CRA by default don't allow we to change the wepback settings.

Let's remember the app:

![Gif of image pokemons list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkftvg801hwh1yrr6v67.gif)

The list of pokemons is a Micro-Frontend (MF), where I will expose the component and the store (pokemon selected). The main page will consume the MF and display the pokemon selected, provided by MF state (using Jotai).

**Summary:**
- [**Introduction**](#introduction)
- [**Module Federations**](#module-federations)
- [**Show me the code**](#show-me-the-code)
- [**Conclusion**](#conclusion)

## Introduction

First, we need to understand what is a MF? And why we you can use this approach. The MF concept was create when we have multiple teams and we need to separate our application components between them.

Each team is responsible to maintain the MF, and can be a component or a MF page route.

Image that exists a team where is responsible to maintain the Home Page and other to maintain the Cart component or Page. So we can scale the application and make it smaller, BUT whe have some trade-off that we will address later.

Creating an MF not so long ago was difficult to create and maintain. But currently creating has become something easy, but maintaining it will depend on the team.

So Webpack 5 introduce the new concept of share components. The Module Federations.

## Module Federations

![Module Federations image with webpack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cy3w7p0udv531d7mllxo.png)

Module Federation is a specific method of enabling what are commonly called “micro frontends” in JavaScript applications using Webpack 5.

According the Webpack documentation:

> 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.
This is often known as Micro-Frontends, but is not limited to that.

Just to remember, the module federations is only available on the version 5 of webpack.

With Module Federations we can share not just components, but states as I mentioned above, using Jotai.

## Show Me The Code

So let's create our application to see how module federations works on Vite. We will have two webapps created using vite, first the `pokemons-list` that will expose the component and state. And the second `pokemons-home` that will consume the MF and allow to select and display the pokémon.

First, let's create our directory using:

```sh
mkdir create-react-app && cd create-react-app
```

And now, we will create our MF using:

```sh
yarn create vite pokemons-list --template react-ts
```

Install the packages on the project created, just using:

```sh
yarn
```

Let's add the jotai as dependency, using:

```sh
yarn add jotai
```

Now the main difference of vite, that we can config using the plugin of module federations on `vite.config.ts`, is that we need to use the **craco**.

**Craco** - `Create React App Configuration Override`, an easy and comprehensible configuration layer for create-react-app.

According with craco NPM documentation:

>Get all the benefits of Create React App and customization without using 'eject' by adding a single configuration (e.g. craco.config.js) file at the root of your application and customize your ESLint, Babel, PostCSS configurations and many more.

So, let's add the craco in our project as dev dependency:

```sh
yarn add -D @craco/craco
```

Now Let's code!

First on `src` folder, I will be creating some foldes called `types`, `components` and `atoms`.

- Types will have only the type of Pokemon definition
- Components will have only one component, that will be de List of pokémons
- Atoms will have the state of our application


![Folders of project image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gqnl337t8dfpgulljkug.png)

So on `src/types`, let's create the `Pokemon.ts`

```ts
export interface IPokemon {
  id: number;
  name: string;
  sprite: string;
}
```

On the `src/atoms`, let's create our state of pokémons, also  I called `Pokemon.ts`.
I will be using Jotai, so I will not delve into the subject, as there is an article where I specifically talk about this state manager.

```ts
import { atom, useAtom } from "jotai";
import { IPokemon } from "../types/Pokemon";

type SelectPokemon = IPokemon | undefined;

export const pokemons = atom<IPokemon[]>([]);
export const addAllPokemons = atom(
  null,
  (_, set, fetchedPokemons: IPokemon[]) => {
    set(pokemons, fetchedPokemons);
  }
);
export const selectPokemon = atom<SelectPokemon>(undefined);

const useSelectPokemon = () => useAtom(selectPokemon);
export default useSelectPokemon;
```

And in our components `src/components/PokemonList`, let's create two files. First the `PokemonList.module.css`.
Let's just use CSS modules to our styles.

```css
.container {
    & > h1 {
    color:#1e3a8a;
    font-size: 25px;
    };

    display: flex;
    flex-direction: column;
    border: 3px solid #1d4ed8;
    width: fit-content;
    padding: 5px 5px;
}
.pokemonCardContainer {
    display: flex;
}

.pokemonCard {
    font-family: Arial, Helvetica, sans-serif;
    color: #fff;
    background-color: #1e3a8a;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    margin: 4px;
    padding: 5px;
    border-radius: 4px;
}

.pokemonCard:hover {
    cursor: pointer;
    background-color: #1d4ed8;
}
```

And I will create the `src/components/PokemonList` the `index.tsx` that will be our MF:

```tsx
import { useEffect } from "react";
import useSelectPokemon, {
  addAllPokemons,
  pokemons as pokemonState,
} from "../../atoms/Pokemon";
import { useAtom } from "jotai";

import style from "./PokemonList.module.css";

const PokemonList = () => {
  const [, addPokemons] = useAtom(addAllPokemons);
  const [pokemons] = useAtom(pokemonState);
  const [, setSelectPokemon] = useSelectPokemon();

  const fetchPokemons = async () => {
    const response = await fetch(
      "https://raw.githubusercontent.com/kevinuehara/microfrontends/main/mocks/pokemonList.json"
    );
    const jsonData = await response.json();
    addPokemons(jsonData);
  };

  useEffect(() => {
    fetchPokemons();
  }, []);

  return (
    <div className={style.container}>
      <h1>Pokémon List Micro Frontend</h1>
      <div className={style.pokemonCardContainer}>
        {pokemons.map((pokemon) => {
          return (
            <div
              className={style.pokemonCard}
              key={pokemon.id}
              onClick={() => setSelectPokemon(pokemon)}
            >
              <img
                src={pokemon.sprite}
                aria-label={`Image of pokemon ${pokemon.name}`}
              />
              <label>{pokemon.name}</label>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default PokemonList;
```

You can clean the `App.tsx`, remove the `App.css` and clean the `index.css`.

On your `App.tsx` you can just call your component:

```tsx
import PokemonList from "./components/PokemonList";

function App() {
  return (
    <>
      <PokemonList />
    </>
  );
}

export default App;
```

Don't worry about the fetch URL, because I'm exposed on my repository to bring 5 pokémons.

Now we are prepared to config our MF. So, now let's config our craco file.

First, create the file `.cracrorc.js` on root of your project. It will be like the vite config:

```js
const { ModuleFederationPlugin } = require("webpack").container;

const deps = require("./package.json").dependencies;

module.exports = () => ({
  devServer: {
    port: 3000,
  },
  webpack: {
    configure: {
      output: {
        publicPath: "auto",
      },
    },
    plugins: {
      add: [
        new ModuleFederationPlugin({
          name: "pokemonList",
          filename: "remoteEntry.js",
          exposes: {
            "./PokemonList": "./src/components/PokemonList",
            "./Pokemon": "./src/atoms/Pokemon.ts",
          },
          shared: {
            ...deps,
            jotai: {
              singleton: true,
              requiredVersion: deps.jotai,
            },
            react: {
              singleton: true,
              requiredVersion: deps.react,
            },
            "react-dom": {
              singleton: true,
              requiredVersion: deps["react-dom"],
            },
          },
        }),
      ],
    },
  },
});
```

We will import the Module Federations from webpack and the dependencies of our package.json.

- **name:** the name of our object of module federation

- **filename:** This is very important, because the build of the app will generate a single file that will be our manifest to expose the componets. (I recommended to use `remoteEntry.js` as default)

- **filename:** This is very important, because the build of the app will generate a single file that will be our manifest to expose the componets.

- **exposes:** The object where we will let's say what we're going to expose. In the example the atom of jotai and the PokemonList component.

- **shared:** It's important because when we have other applications running our MF, we need to provide what is needed to render the MF. In this case, `react`, `react-dom` and `jotai`.

**It's important (different of vite, that we explain that it shared dependencies are singleton)**

And even if the other application that consumes it is in react, the module federations plugin will define the import and if you already have it, it will not reimport.

It's very important when we have a MF that the `runtime sharing` we need to use the craco instead of using react-scripts. Changing the `package.json`:

```json
"scripts": {
    "dev": "DISABLE_ESLINT_PLUGIN=true craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },
```

(obs: I change the `start` script to `dev`)

Now if you try to run the app, we will see some error:

![Error image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v18gz2qqhhnf3dovt97o.png)

`Uncaught Error: Shared module is not available for eager consumption: webpack/sharing/consume/default/react/react`

This will happen because the webpack didn't have time to run and load. To fix this problem let's create a file called `bootloader.tsx` and migrate the content of `main.tsx` to this file.

### src/bootloader

```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

export default {};
```

And on the `main.tsx` just change to import the `bootloader`:

### src/main.tsx

```tsx
import("./bootloader");

export default {};
```

This will fix the problem of webpack loader.

Now, if we try to run:

```sh
yarn dev
```

AND WE WILL HAVE THE MF:

![Pokemon List using module federation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0cenrm66fofe107b8b3.png)

And if you access the link `http://localhost:3000/remoteEntry.js` you will see the remoteEntry manifest file:

![Remote Entry File](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bw8z29xmjhxm3rkdk4cf.png)

And we finished the our first MF, using CRA. Now let's consume, with another CRA app!

Open another terminal and back to the root dir of `vite`. And let's create the `pokemons-home`, using the same command of vite:

```sh
yarn create vite pokemons-home --template react-ts
```

Install the dependencies, using:

```sh
yarn
```

Install the `craco` as a dev dependency using:

```sh
yarn add -D @craco/craco
```

And now let's start setting the `.cracrorc.js`:

```js
const { ModuleFederationPlugin } = require("webpack").container;

const deps = require("./package.json").dependencies;

module.exports = () => ({
  devServer: {
    port: 3001,
  },
  webpack: {
    configure: {
      output: {
        publicPath: "auto",
      },
    },
    plugins: {
      add: [
        new ModuleFederationPlugin({
          name: "pokemonHome",
          filename: "remoteEntry.js",
          remotes: {
            pokemonList: "pokemonList@http://localhost:3000/remoteEntry.js",
          },
          shared: {
            ...deps,
            react: {
              singleton: true,
              requiredVersion: deps.react,
            },
            "react-dom": {
              singleton: true,
              requiredVersion: deps["react-dom"],
            },
          },
        }),
      ],
    },
  },
});
```

Note that that we have some differences. Now we have the `remotes`.

The remotes is where the `remoteEntry` is available. So we have the first app on. Because it's important we define the port to be fixed. And we need to say the name of the MF, before of URL.

`pokemonList@http://localhost:3000/remoteEntry.js`

**It's important remember the name that we defined on the MF.**

I will be removing the `index.css` and refact the `App.css` to:

```css
.container {
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-left: 15px;
}

.pokemon-card-container {
    display: flex;
    align-items: center;
}

.pokemon-name {
    font-weight: bold;
    color: #1e3a8a;
    font-size: 20px;
}

.pokemon-image {
    width: 150px;
}
```

Another thing that is very **important**.
Because we are using real time sharing we don't have types of typescript.
So I will rename the `App.tsx` to `App.jsx`, because when I import the MF the typescript don't complain about typing. (there's a solution for this, but it's out of the box). For this example let's just change the type of file.

```jsx
import PokemonList from "pokemonList/PokemonList";
import usePokemonSelected from "pokemonList/Pokemon";

import "./App.css";

function App() {
  const [pokemon] = usePokemonSelected();

  return (
    <>
      <h3 style={{ color: "#1e3a8a", fontSize: "20px" }}>
        Created using Create React App + Craco
      </h3>
      <PokemonList />
      {pokemon && (
        <div className="container">
          <h1 style={{ color: "#1e3a8a" }}>Selected Pokémon:</h1>
          <div className="pokemon-card-container">
            <img
              src={pokemon?.sprite}
              className="pokemon-image"
              aria-label="Image of Pokemon Selected"
            />
            <label className="pokemon-name">{pokemon?.name}</label>
          </div>
        </div>
      )}
    </>
  );
}

export default App;
```

And **again** we need to change the `main.tsx` to `bootloader.tsx`. Because it will happens the same problem when we try to run the app.

### src/bootloader

```tsx
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

export default {};
```

And on the `main.tsx` just change to import the `bootloader`:

### src/main.tsx

```tsx
import("./bootloader");

export default {};
```

AND HERE WE ARE:

![Gif of our final app using Micro frontend](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qq8g5t44b0b4go8v989c.gif)

We finished our two apps the `pokemons-list` (remote) and the `pokemons-home` (host).

## Conclusion

In summary: If we are using the CRA, we need to use the `craco`, to "extends" the wepback config.

The MF is amazing form to break our compoents and expose to other apps consume.
But with all in the technology world we will have trade-offs.

Imagine that the MF team, implements a bug or the app remoteEntry is not not available. We need to treat and work around the problem.

When we are using a library or components, we are using the concept of *Build Time Sharing*. So the component will be available on build of the app.

- **Pros**
 - Complete Applications
 - Typescript Support
 - Unit or E2E Testing

- **Cons**
 - No Runtime Sharing

But when we are using the MF concepts, we are using the *Run Time Sharing*

- **Pros**
 - Not importing all component of a library
 - Runtime Sharing

- **Cons**
 - Typescript Support
 - Difficult to unit and E2E testing

So you need to think and ask for yourself: I really need MF? Trade-offs... etc..

Module Federation is not the unique solution, for example [`single-spa`](https://single-spa.js.org/)

But recently the community has been adopting the module federations of webpack.

Some references:

- [Module Federations Webpack](https://webpack.js.org/concepts/module-federation/)
- [My repository](https://github.com/kevinuehara/microfrontends-module-federation)
- [Linkedin](https://www.linkedin.com/in/kevin-uehara/)


![Gif of a dog saying bye bye](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6jd8kemz540rqzeqw60d.gif)


Thank you so much for your support and read until here.

If possible share and like this post, it will help me a lot.