一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
大家好,我是程序员_随心,希望能够通过自己的学习输出给你带来帮助。
前言
通过本文我们将学到:
- 为什么我们需要包管理工具?
- 包管理工具都有哪些?各有什么优缺点?
- 常见使用场景及相关命令
正文开始:
提示:以下讲的yarn是V1版本,V2版本文档待整理,如果想看的,戳这里。
一、 为什么我们需要包管理工具呢?
我们在日常开发中,肯定有需要重复性的代码工作,比如说文件读取,文件上传等。之前人们把这些内容封装成了一个一个的依赖,放在了自己的官方网站供大家下载。但是如果我们想使用别人的包,就得去人家的官网上下载,然后再使用。想象一下,假如你的项目需要10个包,那么就需要去10个依赖包官网下载,然后再放到自己的项目中使用,非常的繁琐。
于是呢,一个集中式管理的工具npm诞生了,人们把自己开发好的依赖打包后放到npm托管库中。然后如果我们想使用的话,就直接通过npm的命令行工具install就可以直接使用了,而不用管这些包存放在哪里。还有就是依赖包的相互依赖问题,假如没有npm,我们自己手动管理起来是非常繁琐的。
这里声明下,官方是这样描述npm的:
npm 由三个独立的部分组成:
二、 包管理工具都有哪些?各有什么优缺点?
我们主要聊三个包管理工具:npm、yarn、pnpm
1. npm
npm起初是作为下载和管理Node.js包依赖的方式,但现在也已成为前端JavaScript中使用的工具。
优点:
- 创建时间早,兼容性好
缺点:
- 下载速度慢,因为采用的是串行方式,就是采用队列的形式。而且
npm3依赖解析时,必须先遍历所有的项目依赖关系,然后再决定如何生成扁平的node_modules目录结构。为所有使用到的模块构建一个完整的依赖关系树,这是一个耗时的操作。 npm2会递归生成一个的依赖树,导致了假如相同的后代依赖包被不同祖先依赖包依赖时,相同的后代依赖包会被创建多份。npm3创建的扁平化node_modules会导致依赖幽灵👻,即可以在项目中引用package的依赖包中不存在的依赖包,比如可以用moduleA下的依赖的moduleB(moduleB你并没有直接在package.json中引入)下的test方法npm5之前,包版本有可能是不固定的,会导致团队内的依赖包不统一。npm5后,加入了锁文件package-lock.json后解决了此问题。npm5之前没有离线缓存- 假如有50个项目,这50个项目同时有一个相同的依赖包,那么你的硬盘上就会保存50份该相同依赖包的副本。
npm2到npm3+的依赖下载情况详解:
npm2下载依赖后会递归生成一个的依赖树,导致了假如相同的后代依赖包被不同祖先依赖包依赖时,相同的后代依赖包会被创建多份。如果不太清楚的话,看下面的图就比较清晰了:
根据上图可以看出,moduleA和moduleB同样依赖了moduleD 1.0.2版本,但是它被下载了两次。随着项目越来越复杂,依赖树也会越来越复杂,像moduleD这种重复安装的依赖包也会越来越多,会造成大量的冗余。
npm3为了解决上面的依赖下载重复问题,将node_modules目录改成了扁平化的层级结构。就是把后代依赖平铺到node_modules文件夹的根目录下共享使用。npm3会遍历所有的依赖,依次将依赖放在node_modules的根目录中。但当有某些依赖不兼容时,则继续采用npm2的处理方式,不兼容的放在自己的依赖树中。打个比方:module A,module B依赖了module D V1.0.1, module C依赖了module D V1.0.2,同样用一张图来表示:
但是npm3在执行npm install的时候,按照package.json里依赖的顺序依次解析,上图如果module C的解析顺在module A和module B的前面,那么node_modules树则会不同,如下图所示:
通过上图,我们可以看出,npm3并没有完全解决依赖重复下载的问题。
还有一个问题就是,当只有package.json时,团队中其他人下载该项目的依赖时,包版本是不固定的,因为可能在这段时间里,依赖包进行了版本升级。大家可能觉得这没什么问题,其实不然,版本升级有可能会造成方法调用的不同,导致现有项目编译报错。
为了解决这个问题及npm3的问题,npm 5.0 版本后,npm install后都会自动生成一个package-lock.json的文件。有了package-lock.json文件,则在npm install时,如果package.json和package-lock.json中的版本兼容,就会根据package-lock.json中的版本下载;如果不兼容,则会根据package.json的版本,更新package-lock.json中的版本,以保证package-lock.json中的版本兼容package.json(package.json和package-lock.json后期会详细讲解下)。
最后的问题: 由于扁平化的node_modules,导致了*“依赖幽灵”*的产生,及你可以在项目用用到本不能引用到的方法,举个例子:
假如项目依赖项A,依赖项A中存在依赖项B,B中存在test方法,由于扁平化将B依赖项放到了node_module的根目录下,导致了你可以在项目中使用test方法。
2. yarn
yarn是由Facebook、Google、Exponent 和 Tilde 联合推出了一个新的 JS 包管理工具,主要是为了解决npm5之前的痛点而生。
优点:
- 版本锁定
- 安装速度快,不同于npm的队列执行,yarn采用的是同步(并行)执行所有任务
- 离线缓存(这里必须要有
yarn.lock文件,只要你下载过一次,在你没有网的时候也可以下载,前提是你当前目录下存在之前下载的yarn-lock.json文件) - 网络弹性,重试机制确保单个请求失败并不会导致整个安装失败。
- 全局模块的管理。npm 管理全局模块的方式是通过直接在 /usr/lib/node_modules 下面安装,然后通过软连接连接到 /usr/local/bin 目录下。而 yarn 的做法是选择一个目录,这个目录就是全局模块安装的地方,然后将所有的全局模块当做一个项目,从而进行管理。这个好处就是,你可以直接备份这个目录当中的package.json 和 yarn.lock 文件,从而可以很方便的在另一个地方还原你安装了哪些全局模块。
缺点:
- 假如有50个项目,这50个项目同时有一个相同的依赖包,那么你的硬盘上就会保存50份该相同依赖包的副本。
3. pnpm(performant npm) 高性能的npm
pnpm 使用了一种依赖解决策略:内容可寻址存储。此方法会生成一个嵌套
node_modules文件夹,该文件夹将包存储在您的主文件夹 (~/.pnpm-store/) 上的全局存储中。每个版本的依赖项仅物理存储在该文件夹中一次,构成单一事实来源并节省相当多的磁盘空间。
优点:
-
节省磁盘空间并提升安装速度
- 所有文件都保存在硬盘上的统一的位置
-
支持
monorepos(monorepos 后边打算再出一篇细讲) -
严格,
pnpm默认创建了一个非平铺的node_modules,因此代码无法访问任意包。解决了npm的扁平化导致的依赖幽灵的问题。
缺点:
- 兼容性差,最低支持
Node.js 10
最后我们该如何选择这三个包管理器呢?
我不推荐特定的包管理器。这取决于你如何权衡,比如项目团队的要求。其实你选择选择任何一种包管理,看你自己的兴趣爱好。
三、常见使用场景及相关命令
-
查看源及设置淘宝源(由于包管理的源是国外的,所以设置一个国内的源是很有必要的)
# 查看源 npm config get registry yarn config get registry pnpm config get registry # 设置淘宝源 npm config set registry https://registry.npmjs.org/ yarn config set registry https://registry.npmjs.org/ pnpm config set registry https://registry.npmjs.org/ -
初始化
package.json# npm npm init npm init -y # 直接生成 # yarn yarn init yarn init -y # 直接生成 # pnpm pnpm init pnpm init -y # 直接生成 -
具体应用程序安装依赖
# npm npm install <pacakge_name> npm install <pacakge_name>@<version> npm install <pacakge_name>@<tag> npm install --save-dev <pacakge_name> npm install --save-dev <pacakge_name>@<version> npm install --save-dev <pacakge_name>@<tag> # yarn yarn add <pacakge_name> yarn add <package_name>@<version> yarn add <package_name>@<tag> # pnpm pnpm add <pacakge_name> pnpm add <package_name>@<version> pnpm add <package_name>@<tag> -
移除项目中的依赖
# npm npm uninstall <pacakge_name> # yarn yarn remove <package_name> # pnpm pnpm remove <package_name> -
更新项目中的依赖包
# npm npm update <pacakge_name> # yarn yarn upgrade <package_name> #pnpm pnpm update <package_name> -
安装项目的全部依赖
# npm npm install # yarn yarn install 或 yarn # pnpm pnpm install -
查看项目依赖列表
# npm # node 15版本之前 npm list # 查看所有依赖项 npm list --depth 0 # 查看根依赖项 # node 15版本之后 npm list --depth# 查看所有依赖项 npm list # 查看根依赖项 # yarn yarn list # 查看所有 yarn list --depth 0 # 查看根依赖项 # pnpm pnpm list --depth # 查看所有 pnpm list # 查看根依赖项 -
查看全局安装包
# npm # node 15版本之前 npm list -g # 查看所有依赖项 npm list -g --depth 0 # 查看根依赖项 # node 15版本之后 npm list -g --depth # 查看所有依赖项 npm list -g # 查看根依赖项 # yarn yarn global list # pnpm pnpm list -g -
查看全局安装包位置
# npm ➜ ~ npm root -g /Users/hsm/.nvm/versions/node/v16.13.1/lib/node_modules # pnpm ➜ ~ pnpm root -g /Users/hsm/.nvm/versions/node/v16.13.1/pnpm-global/5/node_modules # yarn 稍微麻烦一点,需要找到global dir 然后在找到其下的node_modules ➜ ~ yarn global dir /Users/hsm/.config/yarn/global ➜ ~ cd /Users/hsm/.config/yarn/global ➜ global ls node_modules package.json yarn.lock -
检验过时的包
# npm ➜ npm outdated Package Current Wanted Latest Location Depended by @vue/cli-plugin-babel 3.12.1 3.12.1 5.0.4 node_modules/@vue/cli-plugin-babel SalesPlatformH5 @vue/cli-plugin-eslint 3.12.1 3.12.1 5.0.4 node_modules/@vue/cli-plugin-eslint SalesPlatformH5 @vue/cli-service 3.12.1 3.12.1 5.0.4 node_modules/@vue/cli-service SalesPlatformH5 # yarn yarn outdated #pnpm pnpm outdated -
运行安全审计
# npm npm audit # yarn yarn audit # pnpm pnpm audit -
让你当前目录下的软件包在系统范围内或其他位置都可访问
# npm # 在包文件夹中将在全局文件夹{prefix}/lib/node_modules/<package>中创建一个符号链接 npm link (in package dir) # 将创建一个从全局安装package-name到node_modules/当前文件夹的符号链接 npm link [<@scope>/]<pkg>[@<version>] # yarn yarn link # pnpm pnpm link -
发布/登录/登出,一系列NPM Registry操作
# npm npm publish/login/logout # yarn yarn publish/login/logout #pnpm pnpm publish/login/logout
拓展
-
npx
从本地
node_modules/.bin或从中央高速缓存执行,安装运行<命令>所需的任何包。默认情况下,NPX将检查
$path中的是否存在于本地项目二进制文件中,并执行该命令。如果找不到,则将在执行之前安装它。
参考
最后
您的每一个点赞及评论都是对我坚持写作最大的支持! 另外希望各位朋友和我交流讨论,如有不对的地方,更希望批评指正!
我是程序员_随心,希望能够通过自己的学习输出给你带来帮助。