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 andjavascript
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 inpackage.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 anode_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 rootpackage.json
file and all the dependencies defined in thepackage.json
files of all the packages in thepackages
folder.In order to do so, yarn will create a
node_modules
folder in the root folder, and then it will create anode_modules
folder inside of each of the packages in thepackages
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.