pnpm Complete Guide | Fast Disk Space Efficient Package Manager

pnpm Complete Guide | Fast Disk Space Efficient Package Manager

이 글의 핵심

pnpm is a fast, disk space efficient package manager for Node.js. It uses a content-addressable store to save disk space and is up to 3x faster than npm.

Introduction

pnpm (performant npm) is a fast, disk space efficient package manager. It solves npm’s performance and disk space issues by using a global content-addressable store.

The Problem with npm

npm/yarn:

# Each project duplicates node_modules
project1/node_modules/lodash  # 2MB
project2/node_modules/lodash  # 2MB
project3/node_modules/lodash  # 2MB
# Total: 6MB for 3 projects

pnpm:

# One copy in global store, hard links in projects
~/.pnpm-store/lodash          # 2MB (once)
project1/node_modules/lodash  # hard link (0 bytes)
project2/node_modules/lodash  # hard link (0 bytes)
project3/node_modules/lodash  # hard link (0 bytes)
# Total: 2MB for 3 projects

1. Installation

# Via npm
npm install -g pnpm

# Via standalone script (recommended)
curl -fsSL https://get.pnpm.io/install.sh | sh -

# Via Homebrew (macOS)
brew install pnpm

# Via Volta
volta install pnpm

Verify installation:

pnpm --version

2. Basic Commands

Install Dependencies

# Install all dependencies
pnpm install

# Add a dependency
pnpm add express

# Add dev dependency
pnpm add -D typescript

# Add global package
pnpm add -g pm2

Remove Dependencies

# Remove package
pnpm remove express

# Remove dev dependency
pnpm remove -D typescript

Update Dependencies

# Update all dependencies
pnpm update

# Update specific package
pnpm update express

# Update to latest (ignore semver)
pnpm update express --latest

3. Speed Comparison

Commandnpmyarnpnpm
Install51s37s16s
Install (cache)33s11s1.5s
Install (lockfile)28s11s5s

pnpm is 2-3x faster!

4. Disk Space Efficiency

# Check disk usage
pnpm store status

# Example output:
# Content-addressable store is at: ~/.pnpm-store
# Total size: 2.5 GB
# Projects using store: 47
# Equivalent npm size: ~15 GB (saved 12.5 GB!)

5. Workspaces (Monorepo)

Setup

# pnpm-workspace.yaml
packages:
  - 'packages/*'
  - 'apps/*'

Project structure:

monorepo/
├── pnpm-workspace.yaml
├── package.json
├── packages/
│   ├── ui/
│   │   └── package.json
│   └── utils/
│       └── package.json
└── apps/
    ├── web/
    │   └── package.json
    └── api/
        └── package.json

Install Dependencies

# Install all workspace dependencies
pnpm install

# Install for specific package
pnpm --filter web add react

# Install for all packages
pnpm -r add lodash

Run Scripts

# Run script in specific package
pnpm --filter web dev

# Run in all packages
pnpm -r build

# Run in parallel
pnpm -r --parallel test

6. Filtering

# Install dependencies for 'web' package
pnpm --filter web install

# Run build for all packages in 'packages' folder
pnpm --filter './packages/*' build

# Run dev for 'web' and its dependencies
pnpm --filter web... dev

# Run build for packages that depend on 'utils'
pnpm --filter ...utils build

7. Scripts

// package.json
{
  "scripts": {
    "dev": "pnpm --filter web dev",
    "build": "pnpm -r build",
    "test": "pnpm -r --parallel test",
    "clean": "pnpm -r exec rm -rf dist"
  }
}

8. Configuration

# .npmrc
# Store location
store-dir=~/.pnpm-store

# Symlink node_modules
node-linker=hoisted

# Strict peer dependencies
strict-peer-dependencies=true

# Auto install peer dependencies
auto-install-peers=true

# Shamefully hoist (for compatibility)
shamefully-hoist=false

# Lockfile location
lockfile=true

9. Migration from npm/yarn

Step 1: Remove old files

rm -rf node_modules package-lock.json yarn.lock

Step 2: Install with pnpm

pnpm install

Step 3: Update CI/CD

# GitHub Actions
- uses: pnpm/action-setup@v2
  with:
    version: 8

- uses: actions/setup-node@v3
  with:
    node-version: 18
    cache: 'pnpm'

- run: pnpm install
- run: pnpm build

10. Real-World Example: Next.js Monorepo

Structure:

nextjs-monorepo/
├── pnpm-workspace.yaml
├── package.json
├── apps/
│   ├── web/
│   │   ├── package.json
│   │   └── next.config.js
│   └── admin/
│       ├── package.json
│       └── next.config.js
└── packages/
    ├── ui/
    │   ├── package.json
    │   └── src/
    └── config/
        └── package.json

pnpm-workspace.yaml:

packages:
  - 'apps/*'
  - 'packages/*'

Root package.json:

{
  "name": "nextjs-monorepo",
  "private": true,
  "scripts": {
    "dev": "pnpm --filter web dev",
    "dev:admin": "pnpm --filter admin dev",
    "build": "pnpm -r build",
    "lint": "pnpm -r lint",
    "test": "pnpm -r test"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "eslint": "^8.0.0"
  }
}

apps/web/package.json:

{
  "name": "web",
  "version": "1.0.0",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "^14.0.0",
    "react": "^18.0.0",
    "ui": "workspace:*"
  }
}

packages/ui/package.json:

{
  "name": "ui",
  "version": "1.0.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "dependencies": {
    "react": "^18.0.0"
  }
}

11. Advanced Features

Patching Packages

# Create patch
pnpm patch [email protected]

# Edit files in temporary directory
# Save and generate patch
pnpm patch-commit /tmp/patch-123

# Patch is saved to patches/[email protected]

Overrides

// package.json
{
  "pnpm": {
    "overrides": {
      "axios": "1.0.0",
      "react@<18.0.0": "18.0.0"
    }
  }
}

Catalog

# pnpm-workspace.yaml
catalog:
  react: ^18.0.0
  typescript: ^5.0.0
// package.json
{
  "dependencies": {
    "react": "catalog:"
  }
}

12. Docker Integration

# Dockerfile
FROM node:18-alpine

# Install pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

WORKDIR /app

# Copy workspace files
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
COPY package.json ./

# Install dependencies
RUN pnpm install --frozen-lockfile

# Copy source
COPY . .

# Build
RUN pnpm build

CMD ["pnpm", "start"]

With layer caching:

FROM node:18-alpine AS deps
RUN corepack enable && corepack prepare pnpm@latest --activate

WORKDIR /app
COPY pnpm-lock.yaml package.json ./
RUN pnpm install --frozen-lockfile --prod

FROM node:18-alpine AS builder
RUN corepack enable && corepack prepare pnpm@latest --activate

WORKDIR /app
COPY pnpm-lock.yaml package.json ./
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm build

FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY package.json ./

CMD ["node", "dist/index.js"]

13. Troubleshooting

Problem: Peer dependency warnings

# Auto install peer dependencies
pnpm add --save-peer react-dom

# Or configure .npmrc
echo "auto-install-peers=true" >> .npmrc

Problem: Module not found

# Use shamefully-hoist for compatibility
echo "shamefully-hoist=true" >> .npmrc
pnpm install

Problem: Slow installation

# Clear cache
pnpm store prune

# Use faster mirror (China)
pnpm config set registry https://registry.npmmirror.com

14. Best Practices

1. Use workspace protocol

{
  "dependencies": {
    "ui": "workspace:*"  // Any version from workspace
  }
}

2. Pin pnpm version

// package.json
{
  "packageManager": "[email protected]"
}

3. Use .npmrc for team settings

# .npmrc
auto-install-peers=true
strict-peer-dependencies=false
shamefully-hoist=false

4. Commit pnpm-lock.yaml

git add pnpm-lock.yaml
git commit -m "chore: update dependencies"

15. Performance Tips

1. Use frozen lockfile in CI

pnpm install --frozen-lockfile

2. Prune store regularly

pnpm store prune

3. Use offline mode

pnpm install --offline

Summary

pnpm revolutionizes Node.js package management:

  • 3x faster than npm
  • Saves disk space with content-addressable store
  • Monorepo support with workspaces
  • 100% compatible with npm packages
  • Strict by default prevents phantom dependencies

Key Takeaways:

  1. Uses hard links to save disk space
  2. 2-3x faster than npm/yarn
  3. Built-in monorepo support
  4. Compatible with all npm packages
  5. Strict dependency resolution

Next Steps:

  • Try npm
  • Learn Yarn
  • Build Monorepos

Resources: