EngineeringNpm

npm, Yarn, and pnpm: What Actually Differs

They all install packages. Where they diverge is the shape of node_modules, and that difference has consequences for disk space and for correctness.

Kushan Manahara

November 10, 2024 · 2 min read

00
npm, Yarn, and pnpm: What Actually Differs

Choosing the right package manager can have a significant effect on your project's performance and efficiency. The install command looks the same in all three, so the differences are easy to miss. They come down to how each one lays out node_modules.

The layout difference

npm and Yarn Classic hoist. Every package, including the dependencies of your dependencies, gets flattened to the root of the modules directory. pnpm does not. It uses symlinks to put only your direct dependencies at the root, and keeps everything else nested.

That sounds like an implementation detail until you hit the consequence. As pnpm's own documentation puts it, under hoisting "source code has access to dependencies that are not added as dependencies to the project." You can import a package you never installed, because something else pulled it in. It works locally and breaks when that transitive dependency changes version or disappears. These are phantom dependencies, and pnpm's layout makes them structurally impossible rather than merely discouraged.

One copy on disk instead of many

The second difference is where the files actually live. With npm, if you have 100 projects using a dependency, you have 100 copies of it on disk. pnpm keeps a single content-addressable store and hard-links files out of it, which as the docs say means they consume no additional disk space.

Updates are incremental at the file level too. If a new version of a 100-file package changes one file, pnpm update adds one file to the store rather than cloning the whole package again.

The install itself runs in three stages: resolve the dependency graph and fetch what is missing into the store, calculate the directory structure, then link everything into place.

Where Yarn Modern sits

Yarn Classic behaves like npm, hoisting into a flat tree. Yarn Modern took a different route with Plug'n'Play, which skips node_modules altogether and resolves packages through a lockfile-driven map. It solves the same strictness problem pnpm solves, by removing the directory rather than restructuring it.

Picking one

  • npm is already installed with Node and needs no decision. Fine for a single project where disk space and strictness are not concerns.
  • pnpm is the one to reach for with multiple projects or a monorepo. The disk savings are real and the strict layout catches a class of bug before it ships.
  • Yarn is worth it if you specifically want Plug'n'Play, or you are on a codebase already invested in it.

The one genuine friction with pnpm is packages that assume a flat node_modules and reach for something they did not declare. That is the phantom dependency problem showing up from the other side, and the public-hoist-pattern setting exists to work around it when you cannot fix the package itself.

Written by

Kushan Manahara

Responses (0)

Verified name, role, and email required before posting.

No responses yet

Be the first to share your thoughts, benchmarks, or feedback above.