Monorepo using pnpm workspaces
This article is the second article of my Monorepo in Javascript Series. In this article, I’m going to delves into the use of pnpm, a fast and efficient package manager, for managing monorepos, offering a step-by-step guide to setup and management.
Post Series Roadmap
1 — What is a Monorepo
2 — Monorepo using pnpm workspaces
3 — How to Setup Nx in a pnpm Workspace
Introduction
Managing dependencies and code for multiple projects can be challenging. pnpm, a fast and efficient package manager, offers robust support for monorepos, making it an excellent choice for managing complex codebases. This article explores how to set up and manage a monorepo using pnpm.
Why Choose pnpm for Monorepos?
pnpm stands out for its unique approach to handling node_modules, creating a single version of each package and hard-linking them across projects. This results in significant disk space savings and faster installations.
Setting Up a Monorepo with pnpm
Install pnpm: Start by installing pnpm globally if you haven’t already:
npm install -g pnpm
Initialize a Workspace: Create a new directory for your monorepo and initialize a pnpm workspace:
mkdir pnpm-monorepo
cd pnpm-monorepo
pnpm init
Configure the Workspace: Create a pnpm-workspace.yaml
file in the root of your monorepo to define the packages in the workspace:
packages:
- 'packages/*'
- 'apps/*'
Create Applications: Create apps
for react and nest apps. And packages
for re-useable libraries:
mkdir apps
mkdir packages
Initialize the React Application: To create the react app using vite
run following command from root of monorepo.
pnpm create vite apps/ui
Initialize the Nest Application: To create the nestjs app using nestjs/cli
run following command from root of monorepo.
pnpx @nestjs/cli new apps/api
Run the react(UI) app:
pnpm --filter ui dev
Once the above command will executed you will see the output like:
Run the nestjs(api) app:
pnpm --filter api start:dev
Once the above command will executed you will see the output like:
Scripts and Automation: Update the package.json file on root. Add some scripts in package.json to start both apps with single command:
"scripts": {
"start:ui": "pnpm --filter ui dev",
"start:api": "pnpm --filter api start:dev",
"start": "pnpm run start:ui & pnpm run start:api",
"build": "pnpm recursive run build",
"test": "pnpm recursive run test"
}
pnpm start:ui // It will run UI app
pnpm start:api // It will run API app
pnpm start // It will run both apps
pnpm test // it will test on all workspaces
Explanation of pnpm commands:
— filter: It will filter the app you want to run in out case ui and api are two apps.
— ui/api: Name of the app
— dev/start:dev: Script in package.json of app. dev is script command in scripts object in ui app. and start:dev is the script command in api app.
— recursive: It will run the specifies command like build and test on all workspaces. If some workspace doesn’t have test script, it will ignore it.
Create a Shared Logger Package: Create a new folder for your shared logger package inside the packages
folder:
mkdir -p packages/logger
cd packages/logger
pnpm init
Implement the Logger Package: Add a simple logger implementation in packages/logger/src/index.ts
:
export const Logger = (message: string) => {
console.log(`${'Logger - ' + new Date().toISOString() + ': ' + message}`)
}
export default Logger;
Add dependencies: Add the dependencies for logger, in our case it’s only typescript. Run the following command on root of project.
pnpm add --filter logger typescript
Update package.json: package.json file of logger should look like:
{
"name": "@pnpmworkspace/logger",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"keywords": [],
"author": "",
"license": "ISC",
"scripts": {
"build": "tsc"
},
"dependencies": {
"typescript": "^5.5.3"
}
}
tsconfig.json:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"allowJs": true,
"declaration": true,
"outDir": "./dist",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}
Build Logger package (Run from root):
pnpm --filter logger build
Add Logger as dependency(Run from root):
pnpm add ./packages/logger --workspace-root
Above command will add the package logger into root node_module
folder.
— workspace-root: It will add package in root node_module
folder.
Use Logger Package: You can use @pnpmworkspace/logger
into both ui and api apps.
import { Logger } from '@pnpmworkspace/logger';
Run both apps:
pnpm start
This ensures that build and test commands are executed for all projects within the monorepo.
Benefits of Using pnpm in a Monorepo
- Efficiency: pnpm’s unique handling of dependencies reduces disk usage and speeds up installations.
- Consistency: Centralized management of dependencies ensures consistency across all projects.
- Scalability: pnpm can handle large and complex monorepos efficiently.
Conclusion
Using pnpm for managing a monorepo simplifies dependency management and improves efficiency. Its unique approach to handling node_modules makes it an excellent choice for projects of any size, ensuring consistency and scalability.
Github repo