Setup Lerna, React, Module Federation & Typescript Monorepo Environment
Tags: typescript, react, webpack, lerna
Published: 2023-08-30 07:51:41

> tldr; Here is the [github repo](https://github.com/IbrahimShamma99/react-lerna-typescript-module-federation-monorepo) for this tutorial.


## Introduction

You are working on a react project, but for whatever reason you opened the components directory and realized that you have hundreds of components that some are related to each other and some are not, do you think you can better?
ofc, you can!

I here want to make a quick guide into project setup using Webpack, in the future I will do a `NextTS` instead of `CRA`.

When to use this guide?

1. You have a `take home project` in your interviews for a senior role that has multiple domains, for example E-commerce with cart and items page, and you want to show you can split the app and split their responsibilities each for an independent team.

2. You learning Lerna, Module federations and the Monorepo world, and you want to have a practical experience with having an up and running environment.


Before we jump in the code let's just look at our toolkit;


### What is a Monorepo?

Based on the definition from the [official site](https://monorepo.tools/)

> A Monorepo is a single repository containing multiple distinct projects, with well-defined relationships.

You can think of your monolith repo as a sub project each has a team, and domain, this team takes ownership for developing, testing & deploying this Monorepo, this structure aims to help mananging the project efficiently.

### What is lerna?

Lerna is the Monorepo tool for typescript, it helps managing different projects and run parallel, and since `v6` they are utilizing Nx caching and command distribution which you can check my series of blogs about [Nx](https://dev.to/ibrahimshamma99/nx-nodereact-the-enterprise-stack-part-1-the-environment-5f97)

### What is Module Federation?

Question, have you imported modules dynamically?
Why are not you just import them right away?

dynamic import helps in improving [FCP](https://web.dev/fcp/), since the browser does it wait all content to be loaded to load the page.

But Module Federation takes anther step, and helps you to import the modules on run time, meaning you can deploy the federated modules independently. This helps more towards teams autonomy.



## Set up Environment!

First we need to create the directory for the project:
```sh
mkdir ./monorepo-tutorial
cd ./monorepo-tutorial
```

```sh
npx lerna init
mkdir ./apps
mkdir ./libs
```

Let's take a moment to understand what we have created.

Firstly, we need to look at our files created

```js
├── apps
├── libs
├── package.json
├── .gitignore
├── lerna.json
```

One thing in particular interesting in mentioning is `workspaces` inside the `package.json`.

> Workspaces is a generic term that refers to the set of features in the npm cli that provides support to managing multiple packages from your local files system from within a singular top-level, root package.

Since we created `/apps` we need to go to `package.json` and use `apps` instead of `packages`
as shown below:
```json
{
  "name": "root",
  "private": true,
  "workspaces": [
    "apps/*"
  ],
  "dependencies": {},
  "devDependencies": {
    "lerna": "^7.2.0"
  }
}
```

Now we install the global dependancies we need

```sh
npm i -D @swc/helpers @swc/core @swc/cli
```

> Personally I see what makes lerna special is that if you are aware of package.json and setting up env with your favorite tools there is no extra setups nor learning curve.



Now let's create react apps, we will have two apps, one called `app1` where it will contain the components. and a host that will connect to our remote app aka `app1` and lazily imports the module to be consumed inside `host`.

> You can have as much remotes as you want.

```sh
cd ./apps
npx create-react-app app1 --template typescript
npx create-react-app host --template typescript
```


### Host Set up

Now let's enter
```sh
cd ./host
```

> The great thing about `lerna` is you can setup each app the way it fits your needs.

#### first we install dependancies

```
npm i -D webpack-merge webpack-dev-server webpack-cli webpack ts-loader npm-run-all html-webpack-plugin @module-federation/typescript
```

Now let's take a moment to understand what we are installing here:

* webpack-dev-server: in order to run the dev-server you need to set up the devServer object in your webpack config.

> webpack-dev-server v4.0.0+ requires node >= v12.13.0, webpack >= v4.37.0 (but we recommend using webpack >= v5.0.0), and webpack-cli >= v4.7.0.

* webpack-merge: Will help in setting up a `base` and override it with `dev` and `prod` configs, this approach will in maintaining consistency in the bundling configuration.

* webpack-cli: it takes where the configs are located and run them.

* ts-loader: a webpack overriding rule that takes typescript and bundles it in the webpack, to be consumed in the browser -in our case I mean-.

* html-webpack-plugin: this plugin will create the script tag inside the public/index.html file. It is essential since we are using SPA.

* @module-federation/typescript: Module federation `v1` still not supporting typescript by nature, but it is a planned feature for `v2` as mentioned by the creator of module-federation.
{% embed https://twitter.com/ScriptedAlchemy/status/1689844199515176961
 %}

#### second we setup webpack

```
mkdir ./configs
touch federationConfig.js
touch webpack.base.js
touch webpack.dev.js
touch webpack.prod.js
```

with the following code [from here](https://github.com/IbrahimShamma99/react-lerna-typescript-module-federation-monorepo/tree/master/packages/host/configs)

I will explain the module federation config in depth in the future.


For now we need to go to `package.json` and set up the following scripts
```json
"scripts": {
    "start": "webpack --watch --config configs/webpack.dev.js",
    "build": "webpack --progress --config configs/webpack.prod.js",
    "serve": "webpack serve --config configs/webpack.dev.js",
    "dev": "npm-run-all --parallel start serve",
    "tscheck": "tsc"
}
```


#### finally we set up the `src`

we go to `/src` and delete all of the files, create the following ones: 

```tsx
// apps/host/src/App.tsx
import React from "react";

const App1 = React.lazy(() => import("app1/App"));

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

export default App;

```

Since we are importing a module from another federated remote, the `ts` compiler will throw an error since it can't find `app1/App` module, to solve this issue add the following:
```tsx
// apps/host/src/react-app-env.d.ts

/// <reference types="react-scripts" />
declare module "app1/App";
```

Now we need to render the app we created above.

```tsx
// apps/host/src/bootstrap.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

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

```

We now need to create index file for the src directory: 

```tsx
// apps/host/src/index.ts

import("./bootstrap");
export {};
```


Now we need to do the same steps we did above for all of the remote `app1` but with few changes on the federation config can be found here:

```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"],
    },
  },
};
```



Now all set up and running!