Why npm, Yarn, pnpm, and Deno Differ in Dependency Management – A Deep Dive

This article examines how npm, Yarn, pnpm, cnpm, tnpm and Deno handle dependency installation, version locking, flattening, and module resolution, highlighting the evolution from nested node_modules to lockfiles and Plug'n'Play, and discusses the trade‑offs of each approach.

Taobao Frontend Technology
Taobao Frontend Technology
Taobao Frontend Technology
Why npm, Yarn, pnpm, and Deno Differ in Dependency Management – A Deep Dive

npm is the default Node.js package manager, and alternatives such as Yarn, pnpm, cnpm and tnpm are also widely used. The article explores the dependency‑management mechanisms of these front‑end package managers.

npm

Running npm install downloads packages to a local cache, extracts them, and builds the node_modules directory. Early versions (v1/v2) used a simple nested structure, leading to “dependency hell” where multiple versions of the same package were duplicated.

node_modules<br/>├── [email protected]<br/>│   └── node_modules<br/>│       └── [email protected]<br/>├── [email protected]<br/>│   └── node_modules<br/>│       └── [email protected]<br/>└── [email protected]<br/>    └── node_modules<br/>        └── [email protected]<br/>

npm v3 introduced flattening (hoisting) to reduce nesting and duplicate installations. It also added a recursive upward lookup algorithm to find modules, which mitigated duplication but created new issues such as phantom dependencies (modules usable without being listed in package.json) and non‑deterministic installations depending on install order.

Phantom Dependency

A package not declared in package.json can still be required because its parent’s dependency was hoisted to the top level.

var A = require('A');
var B = require('B'); // ???

Incompatible versions may be pulled in silently.

Dev‑dependency sub‑dependencies can be missing at runtime.

Non‑Determinism

Because npm v3’s tree depends on install order, the same package.json can produce different node_modules layouts on different machines.

npm v5 – Lockfiles

npm v5 adds package-lock.json, which records exact versions, URLs and integrity hashes for every dependency, ensuring deterministic and repeatable installs.

Semantic Versioning

Major – breaking changes

Minor – backward‑compatible features

Patch – backward‑compatible bug fixes

Version ranges ( ~, ^, *) are resolved at install time, but the lockfile pins concrete versions.

Yarn

Yarn (2016) aims for speed, security and reliability. Yarn v1 creates a yarn.lock file with a custom format and a node_modules layout identical to npm v5.

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"

Differences from npm lockfiles: Yarn’s format is not JSON, it retains version ranges, and it requires both package.json and yarn.lock to fully determine the tree.

Yarn v2 – Plug'n'Play

Yarn 2 replaces node_modules with a .pnp.cjs file that maps each package to its location on disk, eliminating I/O‑heavy lookups. The mode is enabled with yarn --pnp. Advantages include faster installs and no duplicate packages; drawbacks are reduced compatibility with tools expecting node_modules.

pnpm

pnpm (2017) uses hard links and symlinks to store a single copy of each package in a global store, then creates a non‑flat node_modules structure that mirrors the true dependency DAG, solving phantom and multiple‑dependency problems.

Hard links save disk space, while symlinks create the nested layout. The approach ensures that only declared dependencies are reachable.

cnpm and tnpm

cnpm is a Chinese mirror of the npm registry; tnpm builds on cnpm for Alibaba’s internal use. Both adopt pnpm‑like symlinked structures but cnpm does not use hard links. tnpm’s rapid mode adds a FUSE‑based virtual filesystem to improve performance and address symlink compatibility.

Deno

Deno discards npm, package.json and node_modules. Dependencies are imported directly via URLs and cached globally. Developers typically create a dep.ts file that re‑exports needed remote modules, then import from that file in the project.

import * as log from "https://deno.land/[email protected]/log/mod.ts";
// dep.ts
export { assert, assertEquals, assertStringIncludes } from "https://deno.land/[email protected]/testing/asserts.ts";

// index.ts
import { assert } from "./dep.ts";

While Deno’s approach eliminates node_modules and lockfiles, it introduces verbose URL imports, security concerns, and a less mature ecosystem.

Conclusion

No current solution perfectly balances determinism, performance, and compatibility. The evolution of npm, Yarn, pnpm, cnpm/tnpm and Deno reflects ongoing learning and optimization in front‑end engineering, and future tools will likely build on these ideas.

References

node_modules困境 (https://zhuanlan.zhihu.com/p/137535779)

How Npm Works (https://npm.github.io/how-npm-works-docs/index.html)

Yarn: Plug'n'Play (https://yarnpkg.com/features/pnp)

pnpm: 基于符号链接的 node_modules 结构 (https://pnpm.io/zh/symlinked-node-modules-structure)

tnpm rapid 模式 (https://zhuanlan.zhihu.com/p/455809528)

Deno linking to third party code (https://deno.land/[email protected]/linking_to_external_code)

dependency managementpnpmnpmYaRNfrontend-development
Taobao Frontend Technology
Written by

Taobao Frontend Technology

The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.

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.