npm yarn pnpm
npm
NPM 是最初由 Node.js 项目开发的 JavaScript 包管理器。它使开发人员能够更轻松地在不同项目之间共享代码,并在自己的项目中使用其他人的代码。安装node.js会带npm
npm get cache // 获取缓存地址
npm -g root // 获取全局安装node_modules地址
npm install -g npm // npm升级
npm install 过程
-
先检查配置 .npmrc 优先级最高
-
检查有没有package-lock.json文件
- 有判断 跟package.json文件里的版本是否一致 一致就用package-lock.json的版本 不一致就重新安装依赖更新package-lock.json
- 没有 根据package.json构建依赖树
-
检查有没有缓存 缓存存放在.npm目录 package-lock.json的integrity属性存放的是索引 根据这个索引去.npm/Content-v2 目录下找缓存
- 如果有缓存 直接拿到这个缓存包 解压到项目的node_module目录下
- 如果没有缓存 就去仓库下在缓存目录 再从缓存目录解压到node_module目录下
- 更新package-lock.json
npm包安装机制
-
Npm1-npm2
在安装依赖包时采用的是递归安装 ,根据dependencies 和 devDependencies来确定第一层依赖,根据第一层依赖的子模块。递归安装各个模块到子依赖的node modules中,直至依赖不再依赖其他模块。如果a b 同时依赖 c 那么 c会同时安装在a b 中, 导致大量冗余,node modules体积过大。
-
Npm3为了解决问题 把依赖打平 ,扁平化结构,尽量把依赖都放到第一级 重复模块只放一个。当模块存在版本不兼容时,靠前的放在第一级,后面的放在依赖树中(上级依赖的node_modules)。虽然解决了node_modules体积过大的问题,但是带来了新的问题:package.json的依赖顺序放的不同 会导致依赖树也会不同,在代码运行时会出现问题。
-
Npm5为了固定依赖树的顺序 出现了package-lock.json 它与node_modules中的包结构完全一致
{ "name": "cloudcontrol-fe", "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/generator": { "version": "7.18.10", "resolved": "https://registry-mirrors.avlyun.org/repository/npm-public/@babel/generator/-/generator-7.18.10.tgz", "integrity": "sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==", "dev": true, "requires": { "@babel/types": "^7.18.10", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, "dependencies": { "@jridgewell/gen-mapping": { "version": "0.3.2", "resolved": "https://registry-mirrors.avlyun.org/repository/npm-public/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", "dev": true, "requires": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.9" } } } }, } }
Version:版本
resolved:下载地址
Integrity: 包 hash 值 校验文件的一致性 完整性
Requires: 对应子依赖
Dependencies: 与外层结构一样 里面时子依赖 只有当这个子依赖的版本与最外层的依赖版本冲突时才有
-
幽灵依赖 没在package.json中声明 但是仍然能在代码中用到的 这会导致一些错误:依赖丢失,如果a依赖了b 在package.json中没有用到b 在代码中直接用到了b 但是假设a升级后不再依赖b 就会导致代码报错找不到b;不兼容版本 或者假设a升级了 b的版本变了 而代码有不兼容的api 也会导致代码报错。npm 和 yarn 都存在这个问题。
yarn
Yarn 是 JavaScript 的包管理器,由 Facebook 开发。它快速、可靠且安全。yarn是在npm2后出现的为了解决npm依赖安装过慢,嵌套黑洞洞问题,引入了扁平化安装 以及缓存机制 离线策略 yarn-lock
yarn cache dir // 查看yarn的全局缓存目录
yarn install
- Checking 检测 检查是否有package-lock.json 有则提示不要混合使用
- Validating package.json 检测运行环境 package.json 中的os cpu engines
- Resolving packages 解析包
- 收集首层依赖 dependencies devDependencies
- 遍历所有依赖 从首层依赖开始 有yarn.lock的从lock中获取依赖的具体信息 没有的从registry中获取 讲包信息 加入依赖信息Map
- 同上对子依赖案层递归处理
- Fetching packages 获取包
- yarn会比较node_modules下的模块是否符合上面收集到的依赖信息 如果符合 就结束 打印 success Already up-to-date
- Yarn 获取包时会先确定缓存中是否存在 不存在就去registry下载
- 讲下载的压缩包写入缓存目录
- 更新yarn.lock
- Linking Packages 连接依赖 将依赖复制到node_modules
- 处理peerDependecies 当依赖不存在时 中断操作提示用户安装
- 扁平化依赖树 首次出现的包放顶层 后面再出现的其他版本的包放在对应的子依赖树中
- 拷贝依赖树到node_modules中
- Building Packages 执行install 阶段的脚本,编译依赖中的二进制包 构建安装
- 执行 preinstall、 install、postinstall 这是因为有些包需要根据宿主机的环境动态生成模块,例如node-sass 通过node-gyp进行本地构建,通过node-gyp将binding.node格式的二进制文件构建成可以被nodejs执行的代码。由于需要node-gyp 和sass二进制文件 下载会慢,开始是通过修改sass-binary-site变量 指定到内网,node-gyp 需要配置disturl。 node-sass 已启用 用 sass
pnpm
pnpm是为了解决幽灵依赖的问题。PNPM 是一个新的 JavaScript 包管理器,它构建在 npm 之上,以简化节点应用程序中包的安装过程。PNPM 是 NPM 的替代品。它遵循与 NPM 相同的原则,但它具有一些附加功能,使其比其前身更强大。
硬链接 软链接
- 硬链接 是多个文件具有同一个索引结点号(index node) 有多个文件 所有文件同步更新 删除其中一个其他不会有影响 除非所以的都文件被删完
ln 文件1 文件2
创建文件1的硬链接文件2 - 软链接 类似桌面快捷方式 软链接其实是一个文本 里面包含源文件的地址信息。例如1是2的软链接 1和2的index node 不同, 但是1中放着2的路径 可以根据1找到2 1和2是主从关系 删掉2 1是一个无效链接
ln -s 1.txt 2.txt
安装机制
- Store 目录 为了让所以包都只下载一次 不管什么版本 pnpm有个~/.pnpm-store/v3/files目录 存储所有的包 每次项目安装依赖时如果这里存在直接硬链接过去不必重复下载
- project/Node_module/.pnpm目录 虚拟目录 nodejs 访问不到,存储当前项目的包的硬链接 所有包都摊平在这个目录(全是一级),对于子依赖包会放心父级依赖包下的node_modules里面,而这个node_modules里面的包其实是软链接 方便父级依赖包能找到子依赖包
- project/Node_module 里面直接目录存的是package.json声明的包 其实是软链接到.pnpm里面的包
假设一个项目依赖 a 和 b c包 a b又依赖d@1 c包依赖d@2,那么pnpm安装后的目录如下
{
node_modules {
.pnpm {
a@1 {
node_modules {
a: 硬链接 strore/a
d@1: 软链接到 ../../d@1/node_modules/d1
}
}
b@1 {
node_modules {
b: 硬链接 strore/b
d@1: 软链接到 ../../d@1/node_modules/d
}
}
c@1 {
node_modules {
c: 硬链接 strore/c
d@2: 软链接到 ../../d@2/node_modules/d
}
}
d@1 {
node_modules {
d: 硬链接 strore/d
}
}
d@2 {
node_modules {
d: 硬链接 strore/d
}
}
}
a :软链接到 .pnpm/a@1/node_modules/a
b :软链接到 .pnpm/a@1/node_modules/b
c :软链接到 .pnpm/a@1/node_modules/c
}
}
package.json声明的包会在store存一份,项目的.pnpm虚拟目录会存对应的硬链接,node/modules上存.pnpm里面对应包的软链接,方便项目直接应用包;而对于子依赖包会全部摊平在.pnpm目录里面,而对应的依赖树关系会在父依赖包里面存对应的符号链接,方面父依赖包用到。
优点:
- .pnpm是虚拟目录 nodejs访问不到,所有不会存在幽灵依赖,项目里面不能直接访问子依赖;
- 所有依赖都摊平 所有不会导致不同版本的子依赖会下载多次的问题,节省磁盘空间;
- 第二次安装依赖包时采用硬链接 安装速度快;