Why npm, Yarn, pnpm and Deno Manage Dependencies Differently – A Deep Dive

This article analyses the evolution of front‑end package managers—from npm's early nested modules to Yarn's lockfile and Plug'n'Play, pnpm's hard‑link strategy, cnpm/tnpm adaptations, and Deno's URL‑based imports—highlighting their dependency resolution mechanisms, trade‑offs, and remaining challenges.

DaTaobao Tech
DaTaobao Tech
DaTaobao Tech
Why npm, Yarn, pnpm and Deno Manage Dependencies Differently – A Deep Dive

Introduction

The article explores how major front‑end package managers handle dependency management, aiming to help readers understand the underlying mechanisms of npm, Yarn, pnpm, cnpm/tnpm and Deno.

npm v1/v2 – Nested Dependencies

Early npm versions used a simple nested layout. When a project depends on modules A and C, and both require different versions of B, the node_modules tree looks like:

node_modules
├── [email protected]
│   └── node_modules
│       └── [email protected]
├── [email protected]
│   └── node_modules
│       └── [email protected]
└── [email protected]
    └── node_modules
        └── [email protected]

This structure leads to the classic “dependency hell” where the same package version is installed multiple times, wasting disk space.

npm v3 – Flattening (Hoisting)

npm v3 rewrote the installer to hoist shared dependencies to the top level, reducing duplication. The resulting tree is flatter, and npm performs an upward lookup to avoid reinstalling identical versions. While flattening solves many duplication problems, it introduces new issues such as phantom dependencies and version conflicts.

Phantom Dependency

A package may be required at runtime even though it is not listed in package.json. For example, after installing A (which depends on B), require('B') works because B was hoisted, but this can cause incompatibility or missing‑dependency errors when the version of B changes.

Multiple Versions (Doppelgangers)

When two modules depend on different versions of B, both versions may be present after hoisting, leading to duplicated singletons and type conflicts.

Non‑Determinism

Because npm v3 decides where to place a package based on the order of installation, the same package.json can produce different node_modules layouts on different machines.

npm v5 – Lockfile for Determinism

npm v5 introduced package-lock.json, which records the exact version, source URL and integrity hash of every resolved package. This lockfile guarantees that repeated npm install commands produce identical node_modules structures, improving consistency and compatibility.

Yarn

Yarn (released in 2016) was created to address npm v3 shortcomings. It generates a yarn.lock file in a custom, human‑readable format that lists all top‑level dependencies and their exact versions.

A@^1.0.0:
  version "1.0.0"
  resolved "uri"
  dependencies:
    B "^1.0.0"

B@^1.0.0:
  version "1.0.0"
  resolved "uri"

B@^2.0.0:
  version "2.0.0"
  resolved "uri"

Key differences between Yarn and npm lockfiles:

Yarn uses a custom format; npm uses JSON.

Yarn lockfiles keep version ranges (e.g., ^, ~); npm lockfiles store exact versions.

Yarn lockfiles contain less detailed dependency‑tree information.

Yarn Plug'n'Play (PnP)

Yarn 2 introduced PnP, which eliminates node_modules entirely. Instead, Yarn generates a .pnp.cjs file that maps each package to its location on disk and provides a custom resolver for require. This removes the I/O overhead of traversing node_modules, but it requires running code through Yarn’s interpreter and can break compatibility with tools that expect a traditional node_modules layout.

pnpm

pnpm (first released in 2017) solves duplication by storing a single copy of each version in a global store and creating hard links in the project’s node_modules. Symbolic links are used to build a virtual nested structure that mirrors the true dependency DAG.

Example of a hard‑linked layout:

<package-name>@version/node_modules/<package-name>

Because only the exact dependencies are linked, phantom dependencies disappear and multiple versions coexist without duplication.

cnpm and tnpm

cnpm is Alibaba’s mirror of the npm registry; tnpm extends cnpm for internal Alibaba use. Both adopt pnpm‑like symlink strategies to create non‑flattened node_modules, but cnpm does not use hard links and tnpm adds a “rapid” mode based on a user‑space file system (FUSE) to improve compatibility of symlinks.

Deno

Deno abandons the npm ecosystem entirely. Dependencies are imported via URLs that embed the module name, version and path, e.g.

import * as log from "https://deno.land/[email protected]/log/mod.ts";

Projects typically create a dep.ts file that re‑exports all required symbols, allowing local modules to import from a single source instead of many remote URLs. While this eliminates node_modules and lockfiles, it introduces verbosity, security concerns, and a less mature ecosystem.

Conclusion

None of the existing solutions fully solves the dependency‑management problem. The evolution from nested npm to flattened npm, Yarn, pnpm, cnpm/tnpm and Deno shows a continuous trade‑off between disk usage, install speed, determinism and ecosystem compatibility. Future advances will likely combine the best aspects of these approaches.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontenddependency managementpnpmnpmYARNnode_modulesDenolockfile
DaTaobao Tech
Written by

DaTaobao Tech

Official account of DaTaobao Technology

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.