Intoroduction into Module Federation, the React Parts
Tags: react, webpack, typescript, javascript
Published: 2023-09-04 19:08:26

In Webpack `V5`, the maintainers introduced `module-federation`. And that's our talking point, we will dive with the parts that concerns the react developers.

## Our agenda
  * [The Idea behind module federation](#the-idea)
  * [Key elements of the plugin](#plugin-elements)
  * [Env Setup](#set-up-env)
  * [Share Components](#share-component)
  * [Share Store](#share-slice)
  * [Task](#task)

### The Idea behind module federation <a name="the-idea"></a>

Let's say that we have a two teams working in e-commerce app specifically the search results page where the related items being rendered, let's call them `A` & `B` with the respected responsibilities.

`A`: They are the UI people, they are responsible of UI components like the `itemCard`.

`B`: are responsible of getting the search results, and mapping the results into the itemsCard.

Our question is, what are our possibilities to manage these teams?

> Now I need you to think of an solution before going below.

### Possible Solution 1:

creating an npm package for the UI Kit where team `B` can download and consume.

obviously this solution has one big drawback that when Team `A` makes a new version, Team `B` has to `upgrade` the package manually to utilize the new version.

Not the optimal solution for our case where we need a full autonomy for each team.

### Possible Solution 2:

Let's make a monorepo, where we have an `app` for team `B` and lazy loads the `lib` made by team `A`, this solution looks valid, and I had used it in my previous projects, but it has one drawback, if UI team modified the UI kit, we need to rebuild the application in order to get the newer version, since the lib is imported into the code, during build time it will be included in the site itself, okay to solve this issue. 

### let me introduce `module-federation`.

Where we can have multiple independent sites/apps/builds and we can share code between them during runtime, this is huge because now Team `A` can push changes and create new build, and when a new user comes in they will make the site `on runtime` get the UI components where team `A` hosted them, without team `B` interference.

### Key things you can achieve with module federation.

- Ability to split UI by domain, and let responsible team to take full ownership.
- A/B code Testing out of the box: where you can have two or more services and choose which to use.
- Run multiple frameworks in the same web app at once without headache, since module federation.

### Plugin key elements <a name="plugin-elements"></a>

`name`: This is the name of the application. Never have conflicting names, always make sure every federated app has a unique
name.
`filename`: This is the filename to use for the remote entry file. The remote entry file is a manifest of all of the exposed modules
and the shared libraries.
`remotes` These are the remotes this application will consume.
`exposes` These are files this application will expose as remotes to other applications.
`shared` These are libraries the application will share with other applications.

### Env Setup <a name="set-up-env"></a>

We will use `lerna`, it is the monorepo tool of choice for most of react teams, there is also Nx, lerna uses Nx behind the scenes but lerna is simple, Nx has its own learning curve, if you would like to learn about Nx I have a dedicated series for it [here](https://dev.to/ibrahimshamma99/nx-nodereact-the-enterprise-stack-part-1-the-environment-5f97)

> In case you have never worked with monorepos [check this out](https://monorepo.tools/)

Let's go to our repo [here](https://github.dev/IbrahimShamma99/gentle-intro-module-federation-react)

> If you would to learn more about the set up by taking a step on step setup Please go [here](https://dev.to/ibrahimshamma99/setup-lerna-react-module-federation-typescript-monorepo-environment-11c6)


### Share Components <a name="share-components"></a>

Let's explore our repo,

if you look at `apps` dir, we have two react apps, `host` that will contain our main app, and `app1` which it will expose components into be used in in our `host`.

Let's export our App.tsx for the `host`, simply we need do the followings:

```js
//apps/app1/configs/federationConfig.js

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

module.exports = {
  name: "app1",
  filename: "remoteEntry.js",
  exposes: {
    "./App": "./src/App"
  },
  shared: {
    ...dependencies,
    react: {
      singleton: true,
      requiredVersion: dependencies["react"],
    },
    "react-dom": {
      singleton: true,
      requiredVersion: dependencies["react-dom"],
    },
  },
};
```

Let's explain what we are doing in our case:

- Shared: First we need a single instance of `react`, `react-dom`, so we put the `singleton` property `true`. Since React, and many other libraries, have internal singletons for storing state. These are used by hooks and other elements of the React architecture. If you have two different copies running on the same page, you will get warnings and errors.

- name: This is a name of the build, must be unique.

- filename: The name of the js file that will word as an entry point for the remotes, simply the name of the file that will let the `host` access `app1` build.

- exposes: The `tsx` or `ts` modules that we need to share them.

Now let's set up the host federation config:

```js 
//apps/host/configs/federationConfig.js

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

module.exports = {
  name: "host",
  filename: "remoteEntry.js",
  remotes: {
    app1: "app1@http://localhost:3001/remoteEntry.js",
  },
  shared: {
    ...dependencies,
    react: {
      singleton: true,
      requiredVersion: dependencies["react"],
    },
    "react-dom": {
      singleton: true,
      requiredVersion: dependencies["react-dom"],
    },
  },
};
```

We have two differences between the two configs, first in `host` we are not exposing, we are just consuming, in order to consume modules you need to connect into the `remoteEntry` of the remote in our case our remote called `app1`, the fun part is we can add as many remotes and as many exposed modules as we want.

Now we need to go to the host `App.tsx` to consume the federated module:

```tsx
import React from "react";
const App1 = React.lazy(() => import("app1/App"));

function App() {

  return (
    <>
      <App1 />
      <div className="App">host</div>
      <div>{data}</div>
    </>
  );
}

export default App;
```

As you can see in the `React.lazy` we are lazily or asynchronously importing and rendering the component, we as a `host` we don't know or own the component, it is up to the service `app1` team to handle it.
 
And for typed definitions we need to declare the federated module, since it is not found on local dirs.
To learn more I recommend the [following source](https://github.com/DefinitelyTyped/DefinitelyTyped)

```ts
//react-app-env.d.ts

/// <reference types="react-scripts" />

declare module "app1/App";
```

### Share Store <a name="share-slice"></a>

Let's say we have a dedicated redux store for `app1` and we would like to consume the store in `host`.
We will not share the store itself, instead we will share the `reducers` and `actions` into the `host`.

First from the `app1` side we create a reducer.

```ts
//apps/app1/src/reducer.ts

import { createSlice } from "@reduxjs/toolkit";

type Theme = "light" | "dark";

export interface LayoutState {
  theme: Theme;
}

const initialState: LayoutState = {
  theme: "light",
};

const layoutSlice = createSlice({
  name: "layout",
  initialState,
  reducers: {
    toggleTheme: (state, action) => {
      state.theme = action.payload as Theme;
    },
  },
});

export const { toggleTheme } = layoutSlice.actions;

export { layoutSlice };

export default layoutSlice;
```

Finally from the `app1` side, we go to the `federationConfig.js` inside the `app1` and the add the following module:

```js
  exposes: {
    "./App": "./src/App",
    "./layout-slice": "./src/reducer",
    }
```

Now we need to consume it in the `host`'s store:

```ts
// apps/host/src/store.ts

const federatedSlices = {
  layout: await import("app1/layout-slice").then(
    (module) => module.default.reducer
  ),
};

const initStore = async () => {
  const Store = configureStore({
    reducer: combineReducers({
      ...federatedSlices,
    }),
  });
  return Store;
};
```

We are initializing the store asynchronously, we need to initialize the store before rendering, inside the `bootstrap.tsx`.

```
initStore().then((Store) => {
  root.render(
    <Provider store={Store}>
      <App />
    </Provider>
  );
});
```

But our implementation has its own problems, first we are waiting the `app1`, to share with us the store in order to render the dom, this can go sideways in many cases,
first if `app1` is down or for whatever reason did not share with us the reducer, the store will not be initialized hence the host will be down.

### Special Task for the warriors <a name="task"></a>

You need to figure out a way where if your `app1` is down, your `host` has some sorts of fallback approach and not being down for such an issue in the store.


Looking forward to learn about your solutions!