Article hero: NPM Package Managers Comparison: npm vs Yarn vs pnpm

NPM Package Managers Comparison: npm vs Yarn vs pnpm

Introduction

The JavaScript ecosystem has evolved significantly over the years, and with it, the tools for managing dependencies. This article provides a comprehensive comparison of the four major package managers: npm, Yarn Classic (1.x), Yarn Berry (2.x+), and pnpm, with a special focus on monorepo management capabilities.

TL;DR - Which Package Manager Should I Use?

Quick Decision Guide:

Quick Overview

Package ManagerInitial ReleaseMaintained ByKey Philosophy
npm2010npm, Inc. (Microsoft)Default, batteries-included
Yarn Classic (1.x)2016Meta (Facebook)Fast, reliable, secure
Yarn Berry (2.x+)2020Yarn TeamPlug'n'Play, zero-installs
pnpm2017Zoltan KochanEfficient disk usage, strict

Popularity and Adoption

20102012201420162018202020222024Dominance PeriodInitial ReleaseStable EcosystemGrowth PeriodInitial ReleaseDevelopmentLegacy MaintenanceInitial ReleaseModern FeaturesEmergence PeriodRapid AdoptionnpmYarn ClassicYarn BerrypnpmPackage Manager Evolution Timeline
MetricnpmYarn ClassicYarn Berrypnpm
GitHub Stars25k+41k+(same repo)28k+
Weekly DownloadsDefault with Node.js~3M~500k~2M
Corporate AdoptionUniversalHighGrowingRapid growth
Community SentimentStableLegacyPolarizingEnthusiastic

Usage Statistics (2024)

According to independent reviews and survey results:

The Software House's 2024 State of Frontend Report (over 6,000 developers):
"npm is the most widely used package manager (56.6% of votes), followed by Yarn Classic (21.5%) and pnpm (19.9%)"12.

Installation Speed and Performance

Benchmark Comparison

File SystemRegistryCachePackage ManagerUserFile SystemRegistryCachePackage ManagerUserClean Install (No Cache)Cached Installinstall commandDownload packagesPackage filesExtract filesLink dependenciesInstallation completeinstall commandCheck cacheCache hitCopy/Link from cacheLink dependenciesInstallation complete

Benchmark Comparison

ScenarionpmYarn ClassicYarn Berrypnpm
Clean install (no cache)~28.6s~8.5s~7.0s3–9s
Cached install~1.3s~4.9s~1.3s~0.73s
Monorepo install*HighModerateLowerLowest
Disk space usage100%100%~80%20-30%4567

*Monorepo install values are relative: pnpm is consistently the fastest and most space-efficient, especially as the number of projects grows4567.

Performance Characteristics

Performance Characteristics

npm

Yarn Classic

Yarn Berry

pnpm

Cache Management

Cache Location and Structure

Package ManagerCache LocationStructure
npm~/.npmCompressed tarballs by package910
Yarn Classic~/.cache/yarnCompressed tarballs + metadata11
Yarn Berry.yarn/cache (project)ZIP files, can be committed12
pnpm~/.pnpm-storeContent-addressable storage587

Cache Commands

# npm
# Forcefully clears the entire npm cache
npm cache clean --force
# Verifies cache integrity and cleans up corrupted entries
npm cache verify
# Lists cached packages (deprecated in newer versions, use npm cache verify instead)
npm cache ls

# Yarn Classic
# Removes all cached packages
yarn cache clean
# Shows all cached packages
yarn cache list
# Displays the cache directory path
yarn cache dir

# Yarn Berry
# Removes all cached packages (--all flag clears everything)
# Cache is project-local in .yarn/cache directory
yarn cache clean --all

# pnpm
# Removes unreferenced packages from the store
pnpm store prune
# Shows information about the store (size, integrity)
pnpm store status
# Displays the path to the pnpm store
pnpm store path

Cache Policies

npm

Yarn Classic

Yarn Berry

pnpm

Dependency Structure and Linking

Directory Structures

npm/Yarn Classic - Nested Structure:

project/
├── node_modules/
│   ├── react/
│   │   ├── package.json
│   │   └── node_modules/
│   │       └── loose-envify/
│   ├── react-dom/
│   │   └── node_modules/
│   │       └── loose-envify/
│   └── express/

pnpm - Flat with Symlinks:

project/
├── node_modules/
│   ├── .pnpm/
│   │   ├── react@18.2.0/
│   │   │   └── node_modules/
│   │   │       └── react/
│   │   └── express@4.18.2/
│   ├── react -> .pnpm/react@18.2.0/node_modules/react
│   └── express -> .pnpm/express@4.18.2/node_modules/express

Yarn Berry - Plug'n'Play:

project/
├── .yarn/
│   ├── cache/
│   │   ├── react-npm-18.2.0-abc123.zip
│   │   └── express-npm-4.18.2-def456.zip
│   └── pnp.cjs
├── .pnp.loader.mjs
└── package.json

Local Package Development

# npm
npm link ../my-package
npm link my-package

# Yarn Classic
yarn link
cd ../project && yarn link my-package

# Yarn Berry
yarn link ../my-package

# pnpm
pnpm link ../my-package
# or use workspace protocol

Linking Comparison

FeaturenpmYarn ClassicYarn Berrypnpm
Global links
Relative links
Portal protocol
Workspace protocol

Monorepo Management

Workspace Configuration

npm/Yarn Classic - package.json:

{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

Yarn Berry - package.json:

{
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

pnpm - pnpm-workspace.yaml:

packages:
  - 'packages/*'
  - 'apps/*'
  - '!**/test/**'

Workspace Features Comparison

FeaturenpmYarn ClassicYarn Berrypnpm
Workspace protocol
Selective execution
Parallel execution
Dependency filteringBasicGoodExcellentExcellent
Cross-package bins
Constraints/Rules
Version syncingManualManualConstraintsCatalogs

Monorepo Commands

# npm (v7+)
npm install --workspace=package-a
npm run build --workspaces
npm run test --workspace=package-a

# Yarn Classic
yarn workspace package-a add lodash
yarn workspaces run build
yarn workspaces info

# Yarn Berry
yarn workspace package-a add lodash
yarn workspaces foreach run build
yarn constraints check

# pnpm
pnpm add lodash --filter package-a
pnpm run --recursive build
pnpm run --filter "./packages/**" test

Advanced Monorepo Features

npm

Yarn Classic

Yarn Berry

pnpm

Monorepo Performance

pnpmYarn BerryYarn ClassicnpmInstall Time1x1.5x2.5x3x

CI/CD Integration

GitHub Actions

npm:

- uses: actions/setup-node@v4
  with:
    node-version: '22'
    cache: 'npm'
- run: npm ci

Yarn Classic:

- uses: actions/setup-node@v4
  with:
    node-version: '22'
    cache: 'yarn'
- run: yarn install --frozen-lockfile

Yarn Berry:

- uses: actions/setup-node@v4
  with:
    node-version: '22'
- run: corepack enable
- run: yarn install --immutable

pnpm:

- uses: pnpm/action-setup@v4
  with:
    version: 10
- uses: actions/setup-node@v4
  with:
    node-version: '22'
    cache: 'pnpm'
- run: pnpm install --frozen-lockfile

GitLab CI

npm:

install-npm:
  image: node:22-alpine
  script:
    - npm ci
  cache:
    key: npm-$CI_COMMIT_REF_SLUG
    paths:
      - .npm/

Yarn Classic:

install-yarn-classic:
  image: node:22-alpine
  script:
    - yarn install --frozen-lockfile
  cache:
    key: yarn-$CI_COMMIT_REF_SLUG
    paths:
      - .yarn-cache/

Yarn Berry:

install-yarn-berry:
  image: node:22-alpine
  script:
    - corepack enable
    - yarn install --immutable
  cache:
    key: yarn-berry-$CI_COMMIT_REF_SLUG
    paths:
      - .yarn/cache/

pnpm:

install-pnpm:
  image: node:22-alpine
  script:
    - corepack enable
    - pnpm install --frozen-lockfile
  cache:
    key: pnpm-$CI_COMMIT_REF_SLUG
    paths:
      - .pnpm-store/

Docker Integration

Multi-stage Dockerfile example:

# Use Node.js 22
FROM node:22-alpine AS base

# Install dependencies only when needed
FROM base AS deps
WORKDIR /app

# Copy package files
COPY package*.json ./
# Choose your package manager
RUN npm ci --only=production

# Development image
FROM base AS dev
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]

# Production image
FROM base AS production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
CMD ["npm", "start"]

Dependency Management Commands

Command Comparison Table

ActionnpmYarn ClassicYarn Berrypnpm
Install allnpm installyarn installyarn installpnpm install
Add packagenpm install pkgyarn add pkgyarn add pkgpnpm add pkg
Add dev depnpm install -D pkgyarn add -D pkgyarn add -D pkgpnpm add -D pkg
Remove packagenpm uninstall pkgyarn remove pkgyarn remove pkgpnpm remove pkg
Update packagenpm update pkgyarn upgrade pkgyarn up pkgpnpm update pkg
Update allnpm updateyarn upgradeyarn uppnpm update
Interactive update-yarn upgrade-interactiveyarn upgrade-interactivepnpm update -i
Auditnpm audityarn audityarn npm auditpnpm audit
Clean installnpm ciyarn install --frozen-lockfileyarn install --immutablepnpm install --frozen-lockfile

Upgrading All Dependencies

npm:

# Update to latest allowed by package.json
npm update

# Update to latest versions (requires npm-check-updates)
npx npm-check-updates -u
npm install

Yarn Classic:

# Interactive
yarn upgrade-interactive --latest

# All to latest
yarn upgrade --latest

Yarn Berry:

# Interactive with groups
yarn upgrade-interactive

# Plugin for more power
yarn plugin import interactive-tools
yarn upgrade-interactive

pnpm:

# Interactive
pnpm update --interactive --latest

# All to latest
pnpm update --latest -r

Version Bumping

# npm
npm version patch/minor/major

# Yarn (all versions)
yarn version --patch/--minor/--major

# pnpm
pnpm version patch/minor/major

# Monorepo version bumping
# npm - manual or use tools like lerna
# Yarn Berry - yarn version check --interactive
# pnpm - use changesets
pnpm changeset
pnpm version-packages

Built-in Dependency Problem Solving Tools

Key Commands Comparison

Command TypenpmYarn ClassicYarn Berrypnpm
Security auditnpm audityarn audityarn npm auditpnpm audit
Auto-fixnpm audit fixpnpm audit --fix
Why installednpm explainyarn whyyarn whypnpm why
Deduplicatenpm dedupeyarn dedupeAutomatic
List depsnpm lsyarn listyarn infopnpm list
Check outdatednpm outdatedyarn outdatedyarn up -ipnpm outdated
Verify integritynpm doctoryarn checkBuilt-inpnpm store status

Dependency Resolution & Overrides

Package ManagerField NameExample SyntaxAdvanced Features
npmoverrides"lodash": "4.17.21"Basic version override
Yarnresolutions"**/lodash": "4.17.21"Glob patterns
pnpmpnpm.overrides"foo>bar": "2.0.0"Nested dependencies

Unique Features

npm:

Yarn Classic:

Yarn Berry:

pnpm:

Common Problems Quick Reference

Version Conflicts:

npm ls package-name      # npm
yarn why package-name    # Yarn
pnpm why package-name    # pnpm

Security Issues:

npm audit fix            # npm
yarn upgrade pkg@ver     # Yarn (manual)
pnpm audit --fix        # pnpm

Phantom Dependencies:

Unique Features and Considerations

Dependency Shadowing & Resolution

Shadowing Problem: When different versions of the same package exist in nested node_modules:

app/node_modules/
├── lodash@4.17.21
└── old-lib/node_modules/
    └── lodash@3.10.1  # Shadow dependency
Package ManagerShadow PreventionResolution Method
npmPartialFlattens when possible, use overrides
Yarn ClassicPartialAggressive flattening, use resolutions
Yarn BerryCompleteNo node_modules (PnP mode)
pnpmCompleteStrict isolation by default

Resolution Examples:

Force all lodash to specific version:

"overrides": {"lodash": "4.17.21"}
"resolutions": {"**/lodash": "4.17.21"}
"pnpm": {"overrides": {"lodash": "4.17.21"}}

Target specific dependency paths:

"overrides": {"package-a": {"lodash": "3.10.1"}}
"resolutions": {"package-a/lodash": "3.10.1"}
"pnpm": {"overrides": {"package-a>lodash": "3.10.1"}}

Outstanding Features by Package Manager

npm:

Yarn Classic:

Yarn Berry:

pnpm:

Strict Installation Comparison

Strictness LevelnpmYarn ClassicYarn Berrypnpm
Phantom dependenciesAllowedAllowedPreventedPrevented
Implicit dependenciesAllowedAllowedErrorError
Version conflictsLast winsResolutionsResolutionsStrict
Peer dependenciesWarningWarningError*Auto-install

*Configurable

Summary and Recommendations

Recommendations by Use Case

Use CaseRecommendedWhy
Small projectsnpmSimplicity, no setup
Large monorepospnpmPerformance, disk efficiency
Enterprise with legacyYarn ClassicStability, compatibility
Cutting-edge monoreposYarn BerryAdvanced features
Open source librariesnpmWidest compatibility
Disk-constrained environmentspnpmSpace efficiency
CI/CD optimizationYarn Berry or pnpmSpeed, caching

Migration Paths

# npm → pnpm
npx pnpm import
pnpm install

# Yarn Classic → Yarn Berry
yarn set version berry
yarn install

# Any → pnpm (monorepo)
pnpm import
# Create pnpm-workspace.yaml
pnpm install

Migration Complexity Analysis

Migration from npm to pnpm

Complexity Level: Low to Medium

Steps Required:

  1. Import existing lockfile: npx pnpm import (converts package-lock.json)
  2. Create workspace configuration (for monorepos):
    # pnpm-workspace.yaml
    packages:
      - 'packages/*'
      - 'apps/*'
  3. Update CI/CD scripts: Replace npm commands with pnpm
  4. Handle peer dependencies: pnpm auto-installs them (may require adjustments)

Key Differences to Address:

AspectnpmpnpmMigration Action
Installnpm installpnpm installUpdate scripts
Add packagenpm install pkgpnpm add pkgUpdate scripts
Add dev depnpm install -D pkgpnpm add -D pkgUpdate scripts
Remove packagenpm uninstall pkgpnpm remove pkgUpdate scripts
Updatenpm updatepnpm updateUpdate scripts
Clean installnpm cipnpm install --frozen-lockfileUpdate CI/CD
Workspacespackage.jsonpnpm-workspace.yamlCreate new config file
Workspace runnpm run --workspacespnpm -r runUpdate scripts
Lockfilepackage-lock.jsonpnpm-lock.yamlImport with pnpm import
Node modulesHoistedSymlinkedMay break phantom deps

Potential Issues:

Migration from npm to Yarn Berry

Complexity Level: Medium to High

Steps Required:

  1. Install Yarn Berry: npm install -g yarn && yarn set version berry
  2. Configure PnP or node_modules: Choose installation strategy
  3. Update editor settings: VSCode/IDE support for PnP
  4. Migrate scripts: Update package.json scripts
  5. Configure constraints (optional): Define dependency rules

Key Differences to Address:

AspectnpmYarn BerryMigration Action
Installnpm installyarn installNo change
Add packagenpm install pkgyarn add pkgUpdate scripts
Remove packagenpm uninstall pkgyarn remove pkgUpdate scripts
Updatenpm updateyarn upUpdate scripts
Clean installnpm ciyarn install --immutableUpdate CI/CD
Node modulesPhysical filesPnP (ZIP files)Configure nodeLinker
Cache~/.npm.yarn/cacheProject-local cache
Lockfilepackage-lock.jsonyarn.lockRegenerate on install
Scriptsnpm run scriptyarn scriptOptional optimization
OverridesoverridesresolutionsRename field
Editor supportNativeRequires PnP pluginInstall editor extensions

Configuration Decisions:

# .yarnrc.yml
nodeLinker: pnp # or 'node-modules'
enableGlobalCache: true
enableMirror: true
compressionLevel: mixed

plugins:
  - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
    spec: "@yarnpkg/plugin-interactive-tools"

Potential Issues:

Migration from Yarn Classic to pnpm

Complexity Level: Low to Medium

Steps Required:

  1. Remove Yarn artifacts: rm -rf node_modules yarn.lock
  2. Install pnpm: npm install -g pnpm
  3. Create workspace config: Convert workspaces to pnpm-workspace.yaml
  4. Install dependencies: pnpm install
  5. Update scripts: Replace yarn with pnpm in package.json

Key Differences to Address:

AspectYarn ClassicpnpmMigration Action
Installyarn installpnpm installUpdate scripts
Add packageyarn add pkgpnpm add pkgUpdate scripts
Remove packageyarn remove pkgpnpm remove pkgNo change
Updateyarn upgradepnpm updateUpdate scripts
Interactive upgradeyarn upgrade-interactivepnpm update -iUpdate scripts
Clean installyarn install --frozen-lockfilepnpm install --frozen-lockfileNo change
Workspacespackage.jsonpnpm-workspace.yamlCreate new config file
Workspace runyarn workspaces runpnpm -r runUpdate scripts
Workspace addyarn workspace pkg addpnpm add --filter pkgUpdate syntax
Lockfileyarn.lockpnpm-lock.yamlRegenerate on install
CacheGlobal cacheContent-addressable storeMore efficient storage
Resolutionsresolutionspnpm.overridesMove to pnpm section

Migration Configuration:

# .yarnrc.yml for gradual migration
nodeLinker: node-modules  # Start with familiar structure
enableGlobalCache: true
enableMirror: false

# Gradually enable advanced features
# nodeLinker: pnp
# enableScripts: false
# enableImmutableInstalls: true

Common Migration Challenges

Phantom Dependencies:

# Problem: Code importing undeclared dependencies
import _ from 'lodash' # Not in package.json but works with npm/Yarn

# Solution: Declare all dependencies explicitly
pnpm add lodash

CI/CD Updates:

# GitHub Actions example
- name: Install dependencies
  run: |
    corepack enable
    pnpm install --frozen-lockfile

# or for Yarn Berry
- name: Install dependencies
  run: |
    corepack enable
    yarn install --immutable

Tool Configuration:

TypeScript module resolution for PnP (tsconfig.json):

{
  "compilerOptions": {
    "moduleResolution": "node",
    "types": [
      "node",
      "@types/node"
    ]
  },
  "ts-node": {
    "esm": true
  }
}

Conclusions

Choosing the right package manager is crucial for the efficiency and scalability of your JavaScript projects.

Ultimately, the best choice depends on your specific needs, team expertise, and project complexity. For new projects, pnpm is a strong contender that can save you time and resources in the long run.

Need Help with Your Project?

Optimizing your development workflow is just one part of building a successful application. If you're looking for expert guidance on anything from monorepo strategy and CI/CD pipelines to front-end architecture and performance, I can help.

Contact me today to discuss your project's unique challenges and how we can solve them together.

References

Footnotes

  1. Is npm Enough? Why Startups are Coming after this JavaScript ... (RedMonk) 2 3 4 5

  2. State of Frontend 2024 – The Software House 2 3 4 5

  3. NPM vs Yarn vs PNPM: Why so many? – DEV.to

  4. Why pnpm Is the Superior Choice for JavaScript Package ... – DEV.to 2 3 4 5

  5. pnpm FAQ – pnpm.io 2 3 4 5 6

  6. npm vs Yarn vs pnpm – Which Package Manager Should You Use ... – DEV.to 2 3 4 5 6

  7. Why pnpm uses Hard links instead of soft links? – GitHub 2 3 4 5

  8. Why Switching to pnpm Can Save Your Disk Space ... – Hashnode 2 3 4

  9. How to Clear npm Cache | phoenixNAP KB 2

  10. The Ultimate Guide to Configuring NPM – Stack Abuse 2

  11. How do I set a custom cache path for Yarn? – Stack Overflow 2

  12. Is Yarn supported for bitbucket-pipelines caching? – Atlassian Community 2

Developed by Oleksii Popov
VAT (Austria): ATU81970958
2025