安装
1、安装npm需要安装nodejs,node中自带npm包管理器
node下载地址:下载 | Node.js
2、cnpm安装(需要安装npm)
cnpm是淘宝团队做的npm镜像,淘宝镜像每 10分钟 进行一次同步以保证尽量与官方服务同步。
npm install -g cnpm --registry=https://registry.npm.taobao.org
3、yarn安装(需要安装npm)
npm install -g yarn
官网地址:安装 | Yarn 中文文档
4、pnpm安装(需要安装npm)
npm install -g pnpm
官网地址:安装 | pnpm 中文文档 | pnpm 中文网
npm和yarn的比较
1、并行安装:yarn安装包会同时执行多个任务,npm 需等待上一个任务安装完成才能运行下一个任务(按照在package.json中声明的顺序),所以npm install 下载速度慢,即使是重新 install 时速度依旧慢
2、离线模式:如果你已经安装过一个包,用 yarn 再次安装会从缓存中获取,而 npm 会从网络下载
3、版本锁定:yarn 默认有一个 yarn.lock 文件锁定版本,保证环境统一,npm是通过
package-lock.json
4、更简洁的输出:yarn 安装包时输出的信息较少,npm 输出信息冗余
5、常用指令对比:
npm和yarn同时混用会有什么问题
当我们运行npm i lodash --save
,lodash
将会被当做依赖加入到package.json
中:
"dependencies": {
"loadsh": "^4.17.4"
}
由于package.json文件中的版本号的特点,下面的三个版本号在安装的时候就代表不同的含义。
- "6.0.3"表示安装指定6.0.3版本,
- "~6.0.3"表示安装6.0.X中最新的版本,
- "^6.0.3"表示安装6.X.X中最新的版本,
举个例子:以typescript依赖包为例
1、原项目中使用package.json定义项目中需要依赖的包,这里的typescript版本号为^4.2.4
2、原项目是用npm来进行包管理,从而生成package-lock.json文件,里面存储了各个依赖的具体来源和版本号,其中typescript的版本号为4.2.4,所以今后使用npm进行安装依赖时都会安装typescript的4.2.4版本,不会进行自动升级
3、如开发者使用yarn命令来进行包依赖安装,则package-lock.json文件无效,只看package.json中的文件,但typescript版本号为^4.2.4,从而会安装4.x.x版本中最新版本即为4.6.3版本,同时生成对应yarn.lock文件
4、启动项目会出现typescript类型报错,其原因是因为原项目是在4.2.4typescript版本环境下编写,但使用yarn进行依赖安装把typescript版本自动更新成了 4.6.3版本,同时4.2.4和4.6.3版本的typescript在类型校验上进行了较大的改进和优化,可以检测出更多的类型问题
所以,如有yarn.lock文件而没有package-lock.json文件则项目是以yarn 来进行包管理;
如有package-lock.json文件而没有yarn.lock文件则项目是以npm来进行包管理。
参考文章:同项目混用npm和yarn出现版本自动更新问题_大狼狗的博客-CSDN博客_npm yarn 混用
pnpm
1、在npm3以前,采用的是嵌套安装的方式。假如我们安装依赖B和C时,如果B和C依赖中依赖了 packageD,那么会将 packageD重复安装2次。这将导致 会占据比较大的磁盘空间,且 windows 的文件路径最长是 260 多个字符,这样嵌套是会超过 windows 路径的长度限制的。
2、npm3+和yarn采取铺平的方式,将依赖扁平化,所有的依赖不再一层层嵌套了,而是全部在同一层,这样也就没有依赖重复多次的问题了,也就没有路径过长的问题了。
为什么会采取这样的方式呢?当我们运行require("xxx")
引入外部模块时,
- 如果
xxx
是一个node核心模块,例如fs
、http
等,那么返回node核心模块。 - 如果不是,那么会判断判断当前
node_modules
文件夹是否有此模块,如果有就返回,如果没有就递归往上层的node_modules
目录查找,如果找到就返回,如果到根目录都没找到就报错。
所以当依赖B中的代码使用require("A")
的时候,找不到A,会往上层的文件夹的node_modules
中继续寻找,所以利用node的require机制,我们可以尽可能的把复用的依赖提升到最上层。
a:这样貌似解决了我们的问题,事实上并没有而且引入了更大的问题幽灵依赖(node_modules 中的依赖包在没有 package.json 中声明的情况下使用了其他包的依赖)
如果 A 依赖 B, B 依赖 C,那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖。因此会出现这种非法访问的情况。因为没有显式依赖,万一有一天B不依赖C了,那你的代码也就不能跑了,因为你依赖的这个包C,但是现在不会被安装了。
b:除此之外:一个包是可能有多个版本的,提升只能提升一个,所以后面再遇到相同包的不同版本,依然还是用嵌套的方式。这就是依赖结构的不确定性。比如:如果提升了packageD2.0.0,依赖B和依赖C同时依赖了packageD1.0.0,那么D1.0.0还是会被安装多次。当两个不同的组件调用 packageD 时,它们可能会得到两个不同的库实例,这意味着可能会突然出现两个单例的实例.避免这个问题的解决方案:lock 文件
3、pnpm
Fast, disk space efficient package manager | pnpm
前置知识:
inode:每一个文件都有一个唯一的 inode,它包含文件的元信息,在访问文件时,对应的元信息会被 copy 到内存去实现文件的访问。
hard link:硬链接可以理解为是一个相互的指针,创建的 hardlink 指向源文件的 inode,系统并不为它重新分配 inode。硬链接不管有多少个,都指向的是同一个 inode 节点,这意味着当你修改源文件或者链接文件的时候,都会做同步的修改。每新建一个 hardlink 会把节点连接数增加,只要节点的链接数非零,文件就一直存在,不管你删除的是源文件还是 hradlink。只要有一个存在,文件就存在。(同一个文件的不同引用)
soft link:软链接可以理解为是一个单向指针,是一个独立的文件且拥有独立的 inode,永远指向源文件,这就类比于 Windows 系统的快捷方式。删除源文件,软链接就会失效。修改了软链接或硬链接的文件,另外的硬链接或软链接以及源文件都会发生变化,这里感觉是需要小心的,特别是修改文件以调试的时候,记得还原回去,否则另外一个项目用到的时候,可能会出问题。(新建一个文件,文件内容指向另一个路径)
.pnpm
虚拟存储目录——.pnpm
,所有直接和间接依赖项都链接到此目录中。该目录通过 <package-name>@<version>
来实现相同模块不同版本之间隔离和复用
Store
pnpm在全局通过Store来存储所有的 node_modules 依赖,并且在 .pnpm 中存储项目的hard links
在使用 pnpm 对项目安装依赖的时候,如果某个依赖在 sotre 目录中存在了话,那么就会直接从 store 目录里面去 hard-link,避免了二次安装带来的时间消耗,如果依赖在 store 目录里面不存在的话,就会去下载一次。
假如全局的包变得非常大怎么办?使用方法为 pnpm store prune ,它提供了一种用于删除一些不被全局项目所引用到的 packages 的功能,例如有个包 axios@1.0.0 被一个项目所引用了,但是某次修改使得项目里这个包被更新到了 1.0.1 ,那么 store 里面的 1.0.0 的 axios 就就成了个不被引用的包,执行 pnpm store prune 就可以在 store 里面删掉它了。
原理分析
我们项目中有一个依赖 bar@1.0.0
。bar@1.0.0也有一个依赖 foo@1.0.0。
-
node_modules 下面有 bar@1.0.0 和 .pnpm 目录,没有 foo@1.0.0
-
bar@1.0.0 通过软链接指向
.pnpm/bar@1.0.0/node_modules/bar@1.0.0
。.pnpm/bar@1.0.0/node_modules/bar@1.0.0
又通过硬链接指向 Store -
bar@1.0.0 依赖的foo@1.0.0 会安装在跟自己的同一级,这里的设计,我理解是根据 node 的 require 机制,bar 中 require('foo') 的时候,就会先找到 foo@1.0.0,而不会往上寻找,这样就避免依赖包版本不一致的问题。
.pnpm/bar@1.0.0/node_modules/foo@1.0.0
。并通过软链接指向 .pnpm 下一级的 foo@1.0.0 -
.pnpm/foo@1.0.0
一样通过硬链接指向 Store
参考文章:pnpm 是凭什么对 npm 和 yarn 降维打击的 - 掘金