Setting up a React (Vite) Monorepo project with Yarn Workspaces

Setting up a React (Vite) Monorepo project with Yarn Workspaces

For better scalability, better dependency management, enhanced collaboration and reusability.

🤔 What is a Monorepo Architectural Concept?

Monorepo is an architectural idea in which all of a project's code is kept in a single repository as opposed to being dispersed over several repositories. Code for various features, settings (such as web and mobile), and even languages can be included in this. By keeping all of the code in one location, the objective behind this notion is to simplify dependency management and enhance teamwork. Monorepo also makes it simple to share code among various project components and can streamline the update-releasing procedure. Implementing Monorepo is possible with the aid of tools like Lerna, Yarn Workspaces, and NX Workspaces.

🤔 Benefits of using Monorepo Architectural Concept?

A monorepo architectural design has various advantages for a project:

  • Better collaboration: Team members can work together and share code more easily when the code is all in one location.

  • Better management of dependencies: A monorepo makes it simpler to manage dependencies between different project components. The likelihood that various sections of the code would malfunction as a result of modifications to shared dependencies is decreased.

  • The release process is made simpler: Since a monorepo makes it possible to release updates to the entire project at once rather than having to do so separately for each repository.

  • The codebase has a single source of truth: Monorepo gives the code a single home, making it simpler to comprehend the architecture and design of the codebase.

  • Better scalability: Monorepo makes it possible to expand without having to create new repositories.

  • Reusability: Monorepo enables the sharing of common code between teams and projects, improving reuse and speeding up development.

🤔 What is Yarn Workspaces?

The Yarn package manager has a feature called Yarn Workspaces that enables the management of many packages within a single repository, or workspace.

Developers can simply manage and share dependencies across those packages thanks to the ability to put related packages together in a monorepo structure.

Yarn Workspaces enables you to manage the codebase of your project by having a single root package.json file and several package.json files in various subfolders that contain the code for distinct packages.

Although each package may have its own scripts and dependencies, they may also share these elements with other packages in the workspace.

This makes it simple to manage dependencies in a centralized manner and to share code among various project components.

Additionally, Yarn Workspaces makes it simple to link different packages together so you can test and develop them alone as well as as a component of a larger project. It also enables simple package versioning and publishing to a package manager like npm.

⚙️ The Setup

❗️ In this setup we are going to create a React monorepo setup using yarn workspaces and applications created with vite.

We will be creating this project in three parts. two will be subparts of the main application and one being a common repository, more like a container for reusable components for the other two parts.

Here is a visual representation of the monorepo structure that we are going end up with after completing this demonstration.

Step 1️⃣:

  • Create a new folder and open it with your favourite code editor. This will be the project's root folder.

  • Inside the root folder create a new package.json file and add the following contents to it:

      {
        "private": true,
        "workspaces": {
          "packages": [
            "./packages/main",
            "./packages/app1",
            "./packages/app2",
            "./packages/common"
          ]
        },
        "scripts": {
          "main": "yarn workspace main dev"
        }
      }
    

Step 2️⃣:

  • Create a new folder inside the root and name it packages

Create four more folders inside packages and name them:

  • app1

  • app2

  • common

  • main

main is going to be our project application.

app1 is going to be a sub project (or) a feature of the main application.

app2 is going to be the second sub project (or) a feature of the main application.

common is going to be a repository containing reusable components likely to be used by both app1 and app2.

  • After creating the folders, move into each folder one at a time and run

      npm create vite@latest
    

    to initialize a new project using vite. Follow the prompts and give a suitable choice of framework along with a suitable variant. For this demonstration, I will be choosing react as the framework and javascript as the variant.

  • Once done, the structure of every project folder under packages should look like this:

Step 3️⃣:

  • Clean up and rename the files in the src/ folder of app1, app2 and common.

  • Add the following contents to the following files:

  • app1/src/App1.jsx

      import React from 'react';
      import { Button } from 'common';
    
      export function App1() {
        return (
          <div>
            <h1>App 1</h1>
            <Button text='App1 Button test' />
          </div>
        );
      }
    
  • app1/src/main.jsx

      export { App1 } from './App1';
    
  • app1/package.json

  • add a new property for entry

      {
          ...
            "main": "./src/main.jsx",
          ...
      }
    
      "dependencies": {
      // dependency name should be same as the repo's name in its            package.json
    
      // * symbolizes any version of the package, but you can meantion any version the repository supports
    
          ...
             "common": "*",
          ...
      }
    
  • app2/src/App2.jsx

      import React from 'react';
      import { Button } from 'common';
    
      export function App2() {
        return (
          <div>
            <h1>App 2</h1>
            <Button text='App2 Button test' />
          </div>
        );
      }
    
  • app2/src/main.jsx

      export { App2 } from './App2';
    
  • app2/package.json

      {
          ...
            "main": "./src/main.jsx",
          ...
      }
    
      "dependencies": {
      // dependency name should be same as the repo's name in its            package.json
    
      // * symbolizes any version of the package, but you can meantion any version the repository supports
    
          ...
             "common": "*",
          ...
      }
    
  • common/src/components.jsx

      import React from 'react';
    
      export function Button(props) {
        return <button>{props.text}</button>;
      }
    
  • common/src/main.jsx

      export { Button } from './components';
    
  • common/package.json

      {
          ...
            "main": "./src/main.jsx",
          ...
      }
    
  • No additions under dependencies in this project.

scripts other than build in package.json of app1, app2 and common can be deleted as we won't be starting these vite applications independently. These repositories are meant to be exported and imported into main which will be served to the thin client.

Step 4️⃣:

  • Prepare the main application

  • The structure of the main should be looking like this with the following contents:

  • main/src/App.jsx

      import React from 'react';
      import './App.css';
      import { App1 } from 'app1';
      import { App2 } from 'app2';
    
      function App() {
        return (
          <div>
            <h1>Main Application</h1>
            <App1 />
            <App2 />
          </div>
        );
      }
    
      export default App;
    
  • main/src/main.jsx

      import React from 'react'
      import ReactDOM from 'react-dom/client'
      import App from './App'
      import './index.css'
    
      ReactDOM.createRoot(document.getElementById('root')).render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
      )
    
  • main/package.json

      "scripts": {
          "dev": "vite",
          "build": "vite build",
          "preview": "vite preview"
        },
      // dependency name should be same as the repo's name in its            package.json
    
      // * symbolizes any version of the package, but you can meantion any version the repository supports
      "dependencies": {
          ...
              "app1": "*",
              "app2": "*",
          ...
        },
    

Step 5️⃣:

  • Back inside the root directory, run:

      yarn
    

    to download all the dependencies needed by the whole project. you will notice that a node_modules folder will be added to the root folder. Also a node_modules folder will be added to each of app1, app1, common and main

    ❗️ When you run yarn in the root folder of a monorepo, it will install all the dependencies defined in the root package.json file and all the dependencies defined in the package.json files of all the packages in the packages folder.

    In order to do so, yarn will create a node_modules folder in the root folder, and then it will create a node_modules folder inside of each of the packages in the packages folder.

    This is done to keep the dependencies of each package separated and isolated from each other. By doing so, it will allow each package to have its own version of the dependencies, and avoids conflicts that could happen if they were all installed in a single global node_modules folder.

    Additionally, this allows each package to have its own set of dependencies and to have a specific versions of those dependencies that can be different from other packages, while still being able to share common dependencies and have them in a centralized location.

Step 6️⃣:

  • Fire up the main application using:

      yarn main
    
  • This launches the monorepo application 🎉

💭 Conclusion

In conclusion, implementing a monorepo architecture can significantly enhance teamwork and codebase management. Developers may simply share code and manage dependencies among various areas of the project by keeping all of the code in one repository.

Better scalability, better dependency management, enhanced collaboration, a streamlined release process, and reusability are all possible with monorepo. Monorepo might not be appropriate for all projects, so it's crucial to keep that in mind and always balance the benefits and drawbacks before opting to utilise it.

🔗 GitHub

https://github.com/adeeshsharma/react-monorepo-setup

Did you find this article valuable?

Support Adeesh's Software Engineering Insights by becoming a sponsor. Any amount is appreciated!