pnpm
In the ground since Sun Nov 16 2025
Last watered inSun Nov 16 2025
Related Topics
Referece Links
Recap
pnpm (performant npm) is a fast, disk space efficient package manager that uses a content-addressable store and symlinks to manage dependencies. In monorepo contexts, it provides workspace protocol features that allow packages to reference each other locally without publishing to npm.
Key Achievement: Understanding how pnpm's workspace protocol enables local package linking in monorepos, and how this integrates with Turborepo's build orchestration.
What is pnpm?
pnpm is an alternative to npm and yarn that solves several problems with traditional Node.js package managers:
The Traditional npm Problem
npm and yarn (classic):
Problems:
- Disk space waste - Same package installed multiple times
- Slow installs - Copying files repeatedly
- Phantom dependencies - Can import packages not in package.json
- Hoisting issues - Flattening creates version conflicts
The pnpm Solution
pnpm uses a content-addressable store:
Benefits:
- One global store - Each package version stored once
- Hard links - Files reference the store (instant, no copies)
- Strict dependencies - Can only import what's in package.json
- Fast - 2x faster than npm, comparable to yarn
How pnpm Symlinks Work
When you run pnpm install:
- Download to store: Package goes to ~/.pnpm-store/v3/files/...
- Create virtual store: In node_modules/.pnpm/package@version/
- Hard link: Virtual store hard-links to global store
- Symlink: node_modules/package/ symlinks to virtual store
Example:
Why this matters:
- Installing same package in 10 projects = 1 copy on disk
- Changing project dependencies = just update symlinks (fast!)
- No phantom dependencies - strict resolution
Link Local Package into Turborepo
When building a monorepo package that depends on another local package, you need to tell pnpm to link them together instead of fetching from npm.
The Problem
We're building @peek-a-boo/react-sdk which needs types from @peek-a-boo/core:
Without workspace linking:
- TypeScript error: "Cannot find module '@peek-a-boo/core'"
- pnpm would try to fetch from npm registry (doesn't exist yet)
The Solution: workspace:* Protocol
Add the local package as a dependency using pnpm's workspace protocol:
What workspace:* Means
workspace: - Protocol telling pnpm "this is a local package in the monorepo"
* - Version range meaning "use whatever version the local package defines"
Alternatives:
Recommendation: Use workspace:* for monorepo packages - lets you change versions freely.
Step-by-Step: What I Did
Step 1: Identify the Missing Dependency
Created src/client/types.ts with import:
Ran type check:
Step 2: Add to package.json
Edited packages/react-sdk/package.json:
Why in dependencies, not devDependencies?
- @peek-a-boo/core types are needed at runtime (TypeScript consumers need them)
- devDependencies = only needed during development
- dependencies = bundled or needed by consumers
Step 3: Run pnpm install
What happened:
-
pnpm reads workspace:*
- Looks for @peek-a-boo/core in workspace
- Finds it at packages/core
-
Creates symlink
-
Outputs
Step 4: Verify It Works
Check the actual symlink:
How This Integrates with Turborepo
pnpm handles linking, Turborepo handles build order.
The Build Dependency Graph
Once pnpm creates the symlinks, Turborepo reads package.json dependencies to build a graph:
When you run pnpm build:
-
Turborepo reads the graph:
-
Build order:
-
Caching:
- If core hasn't changed, Turborepo uses cached output
- Only rebuilds what changed
The Complete Flow
Common Patterns
Pattern 1: Shared Types (Our Use Case)
Pattern 2: Shared Utilities
Pattern 3: Development Tools
Adding Packages in a Monorepo
One common question: Where do I run pnpm add? At the root or in the specific package?
The answer depends on who needs the package.
Option 1: Add to Specific Package (Most Common)
When to use: Package is only needed by ONE workspace package.
Example: Adding vite-plugin-dts to react-sdk (TypeScript declaration generator for Vite).
Method A: Using --filter (Recommended)
Run from anywhere in the monorepo:
What happens:
- pnpm finds the package by name (@peek-a-boo/react-sdk)
- Adds vite-plugin-dts to its devDependencies
- Installs only for that package
- Updates package.json automatically
Result:
Method B: Change Directory First
When to use Method B:
- You're already working in that directory
- Running multiple package-specific commands
- Simpler for quick local development
When to use Method A:
- Running from root (common with scripts)
- Want to stay in current directory
- Adding to multiple packages in sequence
Option 2: Add to Root (Shared Dependencies)
When to use: Package is needed by MULTIPLE workspace packages, or for workspace-wide tooling.
Example: Shared Dev Tools
What the -w flag does:
- -w or --workspace-root tells pnpm "install at workspace root"
- Without it, pnpm refuses (safety measure - prevents accidental root installs)
When to install at root:
✅ Good use cases:
❌ Bad use cases:
Rule of thumb: If it's in the root package.json, it should be used by ALL or MOST packages.
Real-World Example: Adding vite-plugin-dts
Let's add vite-plugin-dts to the react-sdk (needed for TypeScript declaration generation).
Step 1: Decide Where It Goes
Question: Who needs vite-plugin-dts?
- Answer: Only @peek-a-boo/react-sdk (it's a Vite plugin for our library build)
Decision: Add to react-sdk package, not root.
Step 2: Choose Method
Using --filter (from root):
Or cd first:
Step 3: Verify Installation
Step 4: Use in Code
Quick Reference
| Command | Where it installs | When to use | |---------|-------------------|-------------| | pnpm add pkg --filter=@scope/name | Specific package (from anywhere) | Most common - package-specific deps | | cd path/to/pkg && pnpm add pkg | Specific package (after cd) | When already in package directory | | pnpm add -D -w pkg | Workspace root | Shared tooling (ESLint, TypeScript, etc.) | | pnpm add -D pkg (from root) | ❌ Error | Safety - use -w flag explicitly |
Common Patterns
Adding Multiple Packages
Check What's Installed Where
Remove Packages
Troubleshooting
Error: "Running this command will add the dependency to the workspace root"
Fix: Add -w flag if you really want root install:
Package not found after install:
Wrong package.json was updated:
Key Takeaways
- pnpm is fast and efficient - Uses symlinks + hard links to global store
- workspace:* - Protocol for linking local monorepo packages
- pnpm handles linking - Creates symlinks in node_modules
- Turborepo handles builds - Orchestrates build order based on those links
- Single source of truth - No duplicate types, shared code lives in one place
Used in Projects
- Peek-a-boo monorepo - All packages use workspace:* for internal dependencies
- Pattern applicable to any pnpm workspace + Turborepo setup
Debugging Tips
Check if symlink exists:
Verify workspace resolution:
Force reinstall:
Check Turborepo graph: