DEV Community

Ibrahim Shamma
Ibrahim Shamma

Posted on • Edited on

Setup Lerna, React, Module Federation & Typescript Monorepo Environment

tldr; Here is the github repo 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

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

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, 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:

mkdir ./monorepo-tutorial
cd ./monorepo-tutorial
Enter fullscreen mode Exit fullscreen mode
npx lerna init
mkdir ./apps
mkdir ./libs
Enter fullscreen mode Exit fullscreen mode

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

Firstly, we need to look at our files created

├── apps
├── libs
├── package.json
├── .gitignore
├── lerna.json
Enter fullscreen mode Exit fullscreen mode

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:

{
  "name": "root",
  "private": true,
  "workspaces": [
    "apps/*"
  ],
  "dependencies": {},
  "devDependencies": {
    "lerna": "^7.2.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we install the global dependancies we need

npm i -D @swc/helpers @swc/core @swc/cli
Enter fullscreen mode Exit fullscreen mode

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.

cd ./apps
npx create-react-app app1 --template typescript
npx create-react-app host --template typescript
Enter fullscreen mode Exit fullscreen mode

Host Set up

Now let's enter

cd ./host
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

second we setup webpack

mkdir ./configs
touch federationConfig.js
touch webpack.base.js
touch webpack.dev.js
touch webpack.prod.js
Enter fullscreen mode Exit fullscreen mode

with the following code from here

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

"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"
}
Enter fullscreen mode Exit fullscreen mode

finally we set up the src

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

// 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;

Enter fullscreen mode Exit fullscreen mode

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:

// apps/host/src/react-app-env.d.ts

/// <reference types="react-scripts" />
declare module "app1/App";
Enter fullscreen mode Exit fullscreen mode

Now we need to render the app we created above.

// 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>
);

Enter fullscreen mode Exit fullscreen mode

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

// apps/host/src/index.ts

import("./bootstrap");
export {};
Enter fullscreen mode Exit fullscreen mode

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:

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"],
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Now all set up and running!

Top comments (1)

Collapse
 
abukhalil95 profile image
Yahya Abu Khalil • Edited

Turborepo (monorepo) might be the proper answer over module federation, if code sharing is the main concern but building the whole repository is a deal breaker.

I could see module federation as a way to split the frontend between teams that communicate with contracts, in a way similar to microservices.

If the frontend team is relatively small or highly communicative, just go with a monorepo! Plus we could share types/validations between the backend and frontend more easily than packagings. wink, wink

Great demo on the module federation! I had a attempt at it using nuxtjs before but ended up working with a monorepo too.