Description
每一个前端工程化项目都有依赖库,而依赖库如何安装、卸载与更新,如何处理依赖冲突,如何管理好依赖...这些是包管理器做的事。常见的包管理器有 npm
,yarn
和 pnpm
。
npm
Npm(Node Package Manager)是 Node.js 默认的包管理器,也是很多项目最常使用的管理器。通常我们的项目目录下会有一个 package.json
文件,这个文件除了描述了当前项目的基本信息,比如作者,版本号,版权方式等,还声明了该项目所引用的依赖库列表,其中比较重要的有两个字段:devDependencies
和 dependencies
,分别对应开发环境的依赖与生产环境的依赖。
当我们执行 npm install
时,会安装我们在 package.json 里声明的所有依赖库。
- 首先检查项目目录中是否有
node_modules
目录,或者有package-lock.json
文件,克隆已存在的依赖树。然后创建一颗空树 - fetch 请求所有 package.json 里声明的依赖,将它们添加到第一步创建的空树中。如果已经存在 node_modules 目录,则比对缺失的依赖,将它们添加到树中
- 然后比对第二步中的依赖树以及第一步中的克隆树,制定一个操作列表,这个列表中就记录着新增、更新、删除的依赖。然后执行操作,最终更新 node_modules
npm 常用命令
npm init
初始化npm install <name>
npm install -g <name>
全局安装npm install --save <name>
安装依赖并添加到 package.json 的 的 dependencies 下npm install --save-dev <name>
安装依赖并添加到 package.json 的 devDependencies 下npm update <name>
更新包npm uninstall <name>
卸载包npm list
查看已安装的包npm view <name>
查看包的详细信息npm init -y
使用默认值快速初始化项目npm run <script-name>
执行脚本npm login
登录npm publish
发布npm unpublish <package-name> --force
npm cache clean --force
清除缓存npm config list
查看 npm 配置
yarn
npm 作为 Node.js 默认的包管理器,已经够用了,那为什么还诞生 yarn 呢?那一定是 npm 用起来还不够趁手,所以程序员们才使劲折腾。具体来说,npm 有个很大的问题是依赖冲突,它的 node_modules 依赖树是不断展开延伸的,比如你安装了 A 包,这个包依赖 B 包的 0.1 版本,然后你还安装了 C 包,这个包依赖 B 包的 0.2 版本,在这种情况下,npm 会执行两次 B 包的安装。一方面,这导致了代码的浮肿,另一方面,node_modules 存在同一个包的不同版本,冲突风险也随之增加。
基于上述的问题,Facebook 的软件折腾师们推出了 yarn。相比 npm,它有以下几个新特性:
- 离线安装:借助缓存,已经安装过的依赖无需从网络中下载,可以直接使用
- 性能和安全性提升:安装过程中并行处理,提高速度,并且有更严格的生命周期脚本执行控制,锁文件中存储了包的校验和,确保每次安装的都是相同的包
- 更简洁的依赖树:相同的依赖会合并,这样就减少了 node_modules 的文件数量,进而减少项目体积
- 锁文件:引入 yarn.lock 文件,即使跨机器,也能保证依赖一致
在 yarn2 版本中,还引入了 Plug'n'Play (PnP) 机制。这个机制主要解决传统 node_modules 里递归安装依赖,导致深层嵌套,依赖重复安装等性能问题,PnP 主要有以下特性:
- 无 node_modules:不再把依赖解压到 node_modules 目录,而是使用一个
.pnp.js
文件,直接映射依赖关系 - 即时解析:当 Node.js 需要请求模块时,PnP 通过上面的映射文件,直接定位到正确的依赖版本,减少了文件查找的成本
- 单一副本:每个依赖版本在磁盘上只会有一个副本,无论它被多个包依赖。
yarn 常用命令
yarn init
初始化yarn add <name> -D
添加到开发依赖yarn global add <name>
全局添加yarn upgrade <name>
升级依赖yarn remove <name>
移除依赖yarn install
安装项目依赖yarn list
列出已安装的包yarn info <name>
查看包信息yarn cache clean
清除缓存yarn run <script-name>
运行脚本
pnpm
pnpm 是更现代的前端包管理方案,它相比 npm, yarn 有着更快的安装速度,更完善的依赖管理机制。pnpm 首先解决了幻影依赖问题,幻影依赖是指没有声明在 package.json 里的包,作为子依赖被某个包顺带安装了,这会导致后续安装更新时的不可控。
pnpm 是如何解决幻影依赖的呢?在 pnpm 里,依赖解析分为三层:
第一层由 Node.js 或者 Webpack, Vite 这些工具进行,它根据 package.json 的定义,依照就近原则,在项目的 node_modules 里寻找包,与 yarn 不同,pnpm 采用非扁平化的依赖树,因此 node_modules 依赖树与 packages.json 是一致的,具有稳定的结构。
第二层是通过软链接,从 node_modules/a 软链接到 node_modules/.pnpm/a@1.0/node_modules/a ,软链接利用的是文件指针寻找,既保持了包结构的稳定,也节约了磁盘空间。
第三层是从 node_modules/.pnpm/a@1.0/node_modules/a 硬链接到本机磁盘的 ~/.pnpm-store/v3/files/a/xxxx ,硬链接的好处在于用一个全局地址存储,避免了多项目重复安装,浪费空间的问题。
通过上面三层寻址,可以看出:如果存在幻影依赖,那么在进行第一层寻址时,就会因为找不到 node_modules 里的包而报错,因为它并没有声明在 package.json 中,也就解决了幻影依赖问题。
关于 ~/.pnpm-store 的路径,可以通过 pnpm store path
获得。
这里还要注意,因为 pnpm 采用全局 store 来存储依赖,并在不同项目里通过硬链接指向全局 store。这意味着,如果你电脑安装了多个项目,如果你在调试 A 项目中的某个包,然后手动改了 node_modules 里的内容,那么会影响到 B , C 等其它项目中的这个包,所以在调试的时候注意别掉坑。这里突然想到,每次遇到项目依赖问题,删掉 node_modules ,清除缓存,重新安装,能解决 95% 以上的问题... 遇事不决清缓存就对了。
pnpm 常用命令
pnpm init
pnpm add <name> -D
添加到开发环境pnpm add <name> -g
添加到全局环境pnpm update <name>
升级包pnpm remove <name>
移除包pnpm install
安装pnpm list
列出包pnpm info <name>
查看包信息pnpm store prune
清除缓存pnpm store path
查看 pnpm 全局存储位置pnpm store list
列出存储的包,缓存的包都会放这里pnpm audit
审查包,查看是否有安全风险pnpm outdated
查看过期的包pnpm why <name>
查看各个包的依赖关系pnpm run <script-name>
执行命令