Managing node_modules efficiently with pnpm and Bun
Learn how modern package managers like pnpm and Bun optimize node_modules storage using global stores and symlinks
TypeScript Masterclass
AVAILABLE NOW with a 50% launch discount!
I’ve been working with Node.js for years, and I’ve always been frustrated by node_modules
folders that take up gigabytes of disk space, slow installation times, and the dreaded “dependency hell” where the same package exists in multiple versions across different parts of your dependency tree.
If you’ve been there, you know what I’m talking about.
Traditional package managers like npm have limitations that can lead to inefficient storage and slow installations. Modern alternatives like pnpm and Bun solve these problems through clever use of global stores and symlinks.
I recently switched to Bun for most of my projects, and the difference is remarkable. Let me show you what I learned.
The Problem with Traditional Package Managers
npm’s Approach
When you run npm install
, it will:
- Download packages to your project’s
node_modules
folder - Create a flat structure (mostly)
- Duplicate packages across different projects
- Can have multiple versions of the same package in different locations
This leads to several issues:
- Disk space waste: The same package is stored multiple times across projects
- Slow installations: Packages are downloaded and extracted repeatedly
- Inconsistent behavior: Different projects might resolve to different versions of the same package
- Large
node_modules
: Each project has its own complete copy of dependencies
I remember having 20+ Node.js projects on my machine, each with their own copy of React, Hono, and other common packages. It was a mess.
How pnpm Solves This
pnpm (performant npm) uses a fundamentally different approach based on a global store and symlinks.
Global Store
pnpm stores all packages in a single global location (usually ~/.pnpm-store
on Unix systems). Each package version is stored only once, regardless of how many projects use it.
This is brilliant. Instead of having 20 copies of Hono across 20 projects, you have just one copy in the global store.
Symlinks and Hard Links
Instead of copying packages to each project’s node_modules
, pnpm creates:
- Hard links from the global store to a project-specific store
- Symlinks from the project’s
node_modules
to the project-specific store
Here’s how it works.
First install it (see https://pnpm.io/installation), then:
# Install a package with pnpm
pnpm add hono
# The structure looks like this:
# project/node_modules/hono -> .pnpm/[email protected]/node_modules/hono
# .pnpm/[email protected]/node_modules/hono -> ~/.pnpm-store/[email protected]
Benefits of pnpm’s Approach
- Space Efficiency: Each package version is stored only once globally
- Fast Installations: No need to download packages that already exist in the global store
- Strict Dependency Resolution: Prevents phantom dependencies (accessing packages not explicitly declared)
- Consistent Behavior: Same package version resolves to the same location across projects
The strict dependency resolution is particularly nice. It prevents you from accidentally importing packages that aren’t explicitly declared in your package.json
.
Example: Comparing Storage Usage
Let me show you a real example. I created two projects with the same dependencies:
# Create two projects with the same dependencies
mkdir project1 project2
cd project1 && npm init -y && npm install hono
cd ../project2 && npm init -y && npm install hono
# Check disk usage
du -sh project1/node_modules # ~50MB
du -sh project2/node_modules # ~50MB
# Total: ~100MB
# Now with pnpm
cd ../project1 && rm -rf node_modules && pnpm install
cd ../project2 && rm -rf node_modules && pnpm install
# Check disk usage
du -sh project1/node_modules # ~1MB (mostly symlinks)
du -sh project2/node_modules # ~1MB (mostly symlinks)
du -sh ~/.pnpm-store # ~50MB (actual packages)
# Total: ~52MB (almost 50% savings!)
That’s a ~50% reduction in disk usage. With more projects, the savings become even more significant.
How Bun Solves This
Bun takes a similar approach but with some additional optimizations.
Global Cache
Bun maintains a global cache at ~/.bun/install/cache
where it stores downloaded packages.
Symlink Strategy
Like pnpm, Bun uses symlinks to avoid duplicating packages:
# Install with Bun
bun install
# Bun creates symlinks from node_modules to the global cache
# project/node_modules/hono -> ~/.bun/install/cache/[email protected]
Additional Optimizations
Bun goes further with several optimizations:
- Native Speed: Written in Zig, Bun is significantly faster than JavaScript-based package managers
- Built-in Bundler: No need for separate bundling tools
- Built-in Test Runner: Integrated testing without additional dependencies
- Better Lockfile: More efficient lockfile format
I’ve been experimenting with Bun lately, and the speed difference is impressive. It’s not just about package installation - it’s faster at running scripts, bundling, and testing too.
Practical Comparison
Let me show you a real comparison I did recently:
# Create a new project
mkdir my-app && cd my-app
npm init -y
# Add some common dependencies
echo '{
"dependencies": {
"hono": "^4.0.0",
"axios": "^1.6.0",
"react": "^18.2.0",
"typescript": "^5.3.0"
}
}' > package.json
# Test with npm
time npm install
du -sh node_modules
# Test with pnpm
rm -rf node_modules package-lock.json
time pnpm install
du -sh node_modules
# Test with Bun
rm -rf node_modules pnpm-lock.yaml
time bun install
du -sh node_modules
Here’s what I got on my machine:
- npm: 18 seconds, 180MB
- pnpm: 6 seconds, 2MB (symlinks)
- Bun: 3 seconds, 2MB (symlinks)
The difference is night and day. Bun was 6x faster than npm, and pnpm was 3x faster.
Migration Guide
From npm to pnpm
Here’s how to move from npm to pnpm:
-
Install pnpm:
npm install -g pnpm
-
Remove existing node_modules:
rm -rf node_modules package-lock.json
-
Install with pnpm:
pnpm install
-
Update scripts (optional):
{ "scripts": { "start": "pnpm run dev", "build": "pnpm run build" } }
That’s it. Your project should work exactly the same, but with faster installs and less disk usage.
From npm to Bun
Here’s how to swap npm with Bun:
-
Install Bun:
curl -fsSL https://bun.sh/install | bash
-
Remove existing files:
rm -rf node_modules package-lock.json
-
Install with Bun:
bun install
When to Use Each
Use npm when:
- Working on legacy projects
- Team is not ready to adopt new tools
- Need maximum compatibility
Use pnpm when:
- Want significant disk space savings
- Need strict dependency resolution
- Working with monorepos
- Want faster installs without changing your workflow much
- Want complete compatibility with Node.js
Use Bun when:
- Starting new projects
- Want maximum performance
- Need built-in bundling and testing
- Team is comfortable with newer tools
- Comfortable using Bun’s engine instead of Node.js
My Recommendation
I’ve been using pnpm for most of my projects lately, and I’m really happy with it. The disk space savings alone make it worth it, and the faster installs are a nice bonus.
For new projects, I’m experimenting with Bun. The performance and features built into it are impressive.
If you’re working on a team, I’d recommend starting with pnpm. It’s more mature and has better tooling support. Once you’re comfortable with it, you can always try Bun for new projects.
Conclusion
Modern package managers like pnpm and Bun represent a significant improvement over traditional approaches. By using global stores and symlinks, they solve the major pain points of Node.js dependency management:
- Reduced disk usage through deduplication
- Faster installations through caching
- More predictable behavior through strict resolution
- Better developer experience overall
While npm remains the most widely adopted package manager, pnpm and Bun offer compelling alternatives that can significantly improve your development workflow, especially for larger projects or teams working on multiple Node.js applications.
The choice between them depends on your specific needs, but both represent the future of efficient Node.js package management.
Give pnpm a try on your next project. I think you’ll be surprised by how much better the experience is.
I wrote 20 books to help you become a better developer:
- JavaScript Handbook
- TypeScript Handbook
- CSS Handbook
- Node.js Handbook
- Astro Handbook
- HTML Handbook
- Next.js Pages Router Handbook
- Alpine.js Handbook
- HTMX Handbook
- React Handbook
- SQL Handbook
- Git Cheat Sheet
- Laravel Handbook
- Express Handbook
- Swift Handbook
- Go Handbook
- PHP Handbook
- Python Handbook
- Linux/Mac CLI Commands Handbook
- C Handbook