前言
在公司项目中,由于比较多地采用了 cnpm 来替换 npm 安装依赖包。使用过程中难免会出现一些关于 cnpm 和 npm 这两者依赖管理这一块各有什么不同的疑问。虽然很多时候并不需要我们去关心这个过程,因为我们一般的使用基本为 npm install [xxx] 这样的安装命令。但事实上,有时候出现的一些依赖包的混乱问题,如果我们不能很清楚地认识到这两者各自的安装原理是什么,势必会造成很多麻烦。
npm
注:目前从依赖方案来划分的话,npm 主要分为 v2 以及 v3 两个版本,而实际使用当中,我们口中的 npm 通常指的是 npm v3。本文中 npm 默认代指 npm v3。
关于 npm 是如何安装解析以及安装依赖包这块,资料是比较充分的,官网也对 npm3 的工作原理进行了详尽的阐述:How npm3 Works。
假设我们现在有一个这样的依赖关系需要安装:
1 | "dependencies": { |
那么最终会被安装为以下目录结构

npm v2
从上面的图当中,npm v2 的时候,所有的包会被递归嵌套安装,这样的安装方式有以下缺点:
- 极大地占用了磁盘存储空间
- 太深的目录树结构会严重影响检索效率
- 不同目录下相同版本的同名依赖包无法复用
其实初始版本的设计也并非无法理解,依赖包之前的版本关系错综复杂,同一个包因为被依赖的关系原因会出现多个版本,npm 设计的初衷也不得不从这些点出发,简单地按照结构安装依赖保证了统一的行为和结构。
npm v3
由于 npm v2 有着众多缺点,官方也提出了新方案,即现在使用最为广泛的 npm v3。npm v3 提出了一种扁平化安装的解决方案:按照 package.json 里依赖的声明顺序对每个依赖进行递归解析,每遇到一个新的包就把它放在第一级目录,如果遇到一级目录已经存在并且版本相同的包,就会忽略安装,否则会退化为 npm2 的方式依次安装在依赖包对应级别的目录下。
扁平化安装带来了诸多优点:在包版本差异不大的情况下,将依赖包都放在一级目录下,由于可以将相同版本的依赖包缓存使用,极大地节省了磁盘空间。
cnpm
cnpm 的来源还得从淘宝的提供的 npm 镜像说起,根本问题是国内网络直连 npm 源不畅,所以淘宝通过其内部的网络来同步国际 npm 镜像并提供给外网使用。本来呢,我们要使用淘宝提供的 npm 镜像并不需要直接使用 cnpm 的,我们可以通过 npm config set registry url 来更换源,也可以通过 nrm 这样 npm 源管理器切换源。但是,cnpm 却不只是将 npm 的源更换成淘宝源这么简单,更准确地说,cnpm 是一个拥有另一套工作原理的 npm 包管理工具。
众所周之,cnpm 的主要特点是快。那么 cnpm 是如何工作的,我们从官方 github 上可以得知,cnpm 使用的是 npminstall 来使得 npm install 变得更快和更简单的。
目录生成规则
从 npminstall 的 README 中可以了解到 cnpm 生成的 node_modules 文件目录结构将遵循以下原则:
- 同名依赖存在多个版本,以 package.json 指定版本的为准(例子二:koa),如果没有则以更新版本为准。(例子一:ms)
- 默认情况下,npminstall 总会尝试安装符合语义版本控制的最新的那个版本的依赖包。(例子一:ms、二:koa)
- 依赖的子依赖将被以软链的形式链接在依赖的
node_modules/${子依赖名}文件夹下,如果 package.json 中没有声明到这些依赖,使用 cnpm ls 查看结构时,这些包会带上extraneous标志。(例子一:ms) - 所有的依赖以及子依赖都会被以隐藏文件夹的形式安装在项目根目录下的
node_modules文件夹下,文件夹名格式为.{version}@{name},eg:.2.2.0@debug。(例子一) --flatten模式下(需手动开启),npminstall 将尝试使用祖先的依赖关系来最小化依赖树。即不同版本如果能互相兼容则不安装其它非必要版本,减小 node_modules 的体积。(例子三)
例子一:base structure
- app:
{ "dependencies": { "debug": "2.2.0", "psleep": "2.2.0" } }(root) - debug@2.2.0:
{ "dependencies": { "ms": "0.7.1" } } - psleep@2.0.0:
{ "dependencies": { "ms": "^0.7.1" } }
1 | app/ |
例子二:latest install
- app:
{ "dependencies": { "koa": "1.1.0", "mod": "1.1.0" } }(root) - mod@1.1.0:
{ "dependencies": { "koa": "~1.1.0" } }
1 | app/ |
例子三:flatten mode
- app:
{ "dependencies": { "koa": "1.1.0", "mod": "1.1.0" } }(root) - mod@1.1.0:
{ "dependencies": { "koa": "~1.1.0" } }
1 | app/ |
npm 与 cnpm(pnpm) 的比较
cnpm 受启发于 pnpm,有着与 pnpm 相似的存储结构,所以当我们比较 cnpm 与 npm 之时实则是在比较 pnpm 与 npm。
npm 从版本3开始维护了一个扁平化的依赖关系树。这导致磁盘空间膨胀更少,但这种方式的副作用是会引起 node_modules 目录的杂乱。
pnpm 则采用软链的形式来管理依赖。 这使得磁盘空间使用更少,同时保持了正确的 node_modules 结构,这样可以避免使用 package.json 中未指定的模块,从而规避一些愚蠢的错误。
npm
- 扁平化安装依赖:无法确保正确的 node_modules 结构
- 减缓了储存空间的膨胀:极端情况下无法有效地减小占用空间
cnpm
- 以软链的形式安装 node_modules:可以确保正确的 node_modules 结构
- 储存空间占用更少:极端情况下依然可以有效缓存
关于两者的不同,可以通过以下链接进行更深入的阅读 pnpm vs npm。
This is copyright.