Npm

npm 是 JavaScript 编写的软件包管理工具,同时也是 node.js 的默认包管理工具。

早期版本

因为早期 npm 版本采用嵌套结构,所以存在依赖地狱和存储空间占用过大问题;同时 Windows 环境下路径限制 256 字符,可能会导致运行出错。

node_modules

V3 版本

V3 版本采用扁平结构来避免过深的依赖树和包冗余,子依赖会尽量平铺在主依赖所在的目录中(同一包但版本号不同还是会放至子目录中)。

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
    └── node_modules
        └── B@2.0.0

虽然解决了依赖地狱的问题,但是又形成了新的问题。

幽灵依赖

幽灵依赖是指在 package.json 中未定义的依赖,但项目中依然可以正确地被引用到。

{
  "dependencies": {
    "A": "^1.0.0",
    "C": "^1.0.0"
  }
}

例如我们只安装了 A 和 C,虽然 A 引用了 B,但是因为平铺在同一目录,所以在项目中引用 B 还是可以正常工作的。将来如果某个版本的 A 依赖不再依赖 B 或者 B 的版本发生了变化,那么就会造成依赖缺失或兼容性问题。

不确定性

node_modules
├── A@1.0.0
├── B@1.0.0
└── C@1.0.0
    └── node_modules
        └── B@2.0.0
node_modules
├── A@1.0.0
│   └── node_modules
│       └── B@1.0.0
├── B@2.0.0
└── C@1.0.0

不确定性是指同样的 package.json 文件,install 依赖后可能不会得到同样的 node_modules 目录结构。如果 A 依赖 B@1.0,C 依赖 B@2.0;依赖安装后究竟该提升 B 为 1.0 还是 2.0 取决于用户的安装顺序。

依赖分身

假设继续再安装依赖 B@1.0 的 D 模块和依赖 @B2.0 的 E 模块,此时:A 和 D 依赖 B@1.0;C 和 E 依赖 B@2.0。

node_modules
├── A@1.0.0
├── B@1.0.0
├── D@1.0.0
├── C@1.0.0
│   └── node_modules
│       └── B@2.0.0
└── E@1.0.0
    └── node_modules
        └── B@2.0.0

可以看到 B@2.0 会被安装两次,实际上无论提升 B@1.0 还是 B@2.0,都会存在重复版本的 B 被安装,这两个重复安装的 B 就叫依赖分身。

安装方式

可以通过 node.js 官网 下载安装,或者直接使用如下脚本。

curl -qL https://www.npmjs.com/install.sh | sh

常用命令

命令功能
npm help帮助文档
npm -vnpm 版本
npm config list -lnpm 配置
npm init初始化引导创建 package.json 文件
npm set <变量名> <值>设置环境变量
npm search <关键词> [-g]检索模块
npm list局部查看模块
npm list -g —depth 0全局安装的模块,目录深度为 0。
npm install读取 package.json 安装模块
npm uninstall <模块> [-g]卸载局部模块或全局模块
npm update <模块> [-g]升级局部模块或全局的指定模块
npm run <脚本>运行 package.json 脚本

Yarn

yarn 同样采用扁平结构,它的出现是为了解决 npm V3 依赖安装速度慢和不确定性。

提升安装速度

yarn 采用并行模式替代 npm 串行模式安装包,并且利用全局缓存可以提升较大安装速度。

Lockfile 解决不确定性

安装依赖时,根据 package.josn 生成一份 yarn.lock 文件。因为 lockfile 里记录了依赖,以及依赖的子依赖、依赖的版本、获取地址、验证模块完整性的 hash;所以即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都能得到统一的 node_modules 目录结构,保证了依赖安装的确定性。

npm v5 才发布 package-lock.json。

存在的问题

因为 yarn 和 npm 一样是扁平结构的 node_modules ,所以并没有解决幽灵依赖依赖分身问题。

安装方式

npm install --global yarn

常用命令

命令功能
yarn help帮助文档
yarn init初始化项目
yarn install安装所有依赖项
yarn add [package]安装指定依赖项
yarn add [package]@[version | tag]安装指定版本依赖
yarn up [package]升级指定依赖
yarn up [package]@[version | tag]升级指定版本依赖
yarn remove [package]删除指定依赖

Pnpm

img

pnpm 不同于 npm 和 yarn 使用的扁平结构,而是采用了内容寻址存储。pnpm 通过设置全局 store,然后在项目中通过使用硬链接与符号链接引用依赖。为了实现此功能,node_modules 目录下会多出 .pnpm 目录,而且是非扁平结构。

  • 硬链接 Hard link:硬链接可以理解为源文件的副本
  • 符号链接 Symbolic link:符号链接(软连接)可以理解为快捷方式
<store>/xxx 开头的路径是硬链接,指向全局 store 中安装的依赖。其余的是符号链接,指向依赖的快捷方式。
 
node_modules
├── .pnpm
│   ├── A@1.0.0
│   │   └── node_modules
│   │       ├── A => <store>/A@1.0.0
│   │       └── B => ../../B@1.0.0
│   ├── B@1.0.0
│   │   └── node_modules
│   │       └── B => <store>/B@1.0.0
│   ├── B@2.0.0
│   │   └── node_modules
│   │       └── B => <store>/B@2.0.0
│   └── C@1.0.0
│       └── node_modules
│           ├── C => <store>/C@1.0.0
│           └── B => ../../B@2.0.0

├── A => .pnpm/A@1.0.0/node_modules/A
└── C => .pnpm/C@1.0.0/node_modules/C

未来可期

pnpm 这套全新的机制设计地十分巧妙,不仅兼容 node 的依赖解析,同时也解决了如下问题:

  • 幽灵依赖:只有直接依赖会平铺在 node_modules 下,子依赖不会被提升,不会产生幽灵依赖。

  • 依赖分身:相同的依赖只会在全局 store 中安装一次。项目中的都是源文件的副本,几乎不占用任何空间,没有了依赖分身。

同时由于链接的优势,pnpm 的安装速度在大多数场景比 npm 和 yarn 快 2 倍,同时也节省更多的磁盘空间。但也存在一些弊端:

  • 因为 pnpm 创建的 node_modules 依赖软链接,所以在不支持软链接的环境中无法使用 pnpm;比如 Electron 应用。

  • 因为依赖源文件是安装在 store 中,调试依赖或 patch-package 给依赖打补丁也不太方便,可能会影响其他项目。

安装方式

Npm

npm install -g pnpm

Curl

curl -fsSL https://get.pnpm.io/install.sh | sh -

常用命令

命令功能
pnpm install安装 package.json 文件中的所有依赖项
pnpm add <pkg>安装指定的依赖项
pnpm update更新所有的依赖项
pnpm uninstall移除指定的依赖项
pnpm list列出已安装的所有依赖项
pnpm run运行在 package.json 文件中定义的脚本
pnpm test运行测试
pnpm link创建或者删除一个软链接到全局安装的依赖项
pnpm prune移除无用的依赖项
pnpm publish将你的包发布到 npm 注册表
pnpm root打印全局安装的依赖项的位置
pnpm store控制共享的包存储
pnpm outdated检查哪些依赖项有新版本可以更新
pnpm rebuild重新编译包
pnpm import从 npm 转换一个项目
pnpm fetch预下载所有从注册表下载的依赖项到本地的存储库
pnpm audit检查项目依赖项中的已知的漏洞

npm,yarn,pnpm 功能比较

功能pnpmYarnnpm
工作空间支持(monorepo)✔️✔️✔️
隔离的 node_modules✔️ - 默认✔️
提升的 node_modules✔️✔️✔️ - 默认
自动安装 peers✔️ - 通过 auto-install-peers=true✔️
Plug’n’Play✔️✔️ - 默认
零安装✔️
修补依赖项✔️✔️
管理 Node.js 版本✔️
有锁文件✔️ - pnpm-lock.yaml✔️ - yarn.lock✔️ - package-lock.json
支持覆盖✔️✔️ - 通过 resolutions✔️
内容可寻址存储✔️
动态包执行✔️ - 通过 pnpm dlx✔️ - 通过 yarn dlx✔️ - 通过 npx
Side-effects cache✔️