跟我一起来学习npm、yarn、pnpm吧

578 阅读10分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

大家好,我是程序员_随心,希望能够通过自己的学习输出给你带来帮助。

前言

通过本文我们将学到:

  1. 为什么我们需要包管理工具?
  2. 包管理工具都有哪些?各有什么优缺点?
  3. 常见使用场景及相关命令

正文开始:

提示:以下讲的yarnV1版本,V2版本文档待整理,如果想看的,戳这里

一、 为什么我们需要包管理工具呢?

我们在日常开发中,肯定有需要重复性的代码工作,比如说文件读取,文件上传等。之前人们把这些内容封装成了一个一个的依赖,放在了自己的官方网站供大家下载。但是如果我们想使用别人的包,就得去人家的官网上下载,然后再使用。想象一下,假如你的项目需要10个包,那么就需要去10个依赖包官网下载,然后再放到自己的项目中使用,非常的繁琐。

于是呢,一个集中式管理的工具npm诞生了,人们把自己开发好的依赖打包后放到npm托管库中。然后如果我们想使用的话,就直接通过npm的命令行工具install就可以直接使用了,而不用管这些包存放在哪里。还有就是依赖包的相互依赖问题,假如没有npm,我们自己手动管理起来是非常繁琐的。

这里声明下,官方是这样描述npm的:

npm 由三个独立的部分组成:

  • 网站

  • 注册表(registry)

  • 命令行工具 (CLI)

    网站 是开发者查找包(package)、设置参数以及管理 npm 使用体验的主要途径。

    注册表 是一个巨大的数据库,保存了每个包(package)的信息。

    CLI 通过命令行或终端运行。开发者通过 CLI 与 npm 打交道。

二、 包管理工具都有哪些?各有什么优缺点?

我们主要聊三个包管理工具:npmyarnpnpm

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份该相同依赖包的副本。

npm2npm3+的依赖下载情况详解:

npm2下载依赖后会递归生成一个的依赖树,导致了假如相同的后代依赖包被不同祖先依赖包依赖时,相同的后代依赖包会被创建多份。如果不太清楚的话,看下面的图就比较清晰了:

npmv2.png

根据上图可以看出,moduleAmoduleB同样依赖了moduleD 1.0.2版本,但是它被下载了两次。随着项目越来越复杂,依赖树也会越来越复杂,像moduleD这种重复安装的依赖包也会越来越多,会造成大量的冗余。

npm3为了解决上面的依赖下载重复问题,将node_modules目录改成了扁平化的层级结构。就是把后代依赖平铺到node_modules文件夹的根目录下共享使用。npm3会遍历所有的依赖,依次将依赖放在node_modules的根目录中。但当有某些依赖不兼容时,则继续采用npm2的处理方式,不兼容的放在自己的依赖树中。打个比方:module A,module B依赖了module D V1.0.1module C依赖了module D V1.0.2,同样用一张图来表示: npmv2

但是npm3在执行npm install的时候,按照package.json里依赖的顺序依次解析,上图如果module C的解析顺在module Amodule B的前面,那么node_modules树则会不同,如下图所示: npmv3

通过上图,我们可以看出,npm3并没有完全解决依赖重复下载的问题。

还有一个问题就是,当只有package.json时,团队中其他人下载该项目的依赖时,包版本是不固定的,因为可能在这段时间里,依赖包进行了版本升级。大家可能觉得这没什么问题,其实不然,版本升级有可能会造成方法调用的不同,导致现有项目编译报错。

为了解决这个问题及npm3的问题,npm 5.0 版本后,npm install后都会自动生成一个package-lock.json的文件。有了package-lock.json文件,则在npm install时,如果package.jsonpackage-lock.json中的版本兼容,就会根据package-lock.json中的版本下载;如果不兼容,则会根据package.json的版本,更新package-lock.json中的版本,以保证package-lock.json中的版本兼容package.json(package.jsonpackage-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

最后我们该如何选择这三个包管理器呢?

我不推荐特定的包管理器。这取决于你如何权衡,比如项目团队的要求。其实你选择选择任何一种包管理,看你自己的兴趣爱好。

三、常见使用场景及相关命令

  1. 查看源及设置淘宝源(由于包管理的源是国外的,所以设置一个国内的源是很有必要的)

    # 查看源
    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/
    
    
  2. 初始化package.json

    # npm
    npm init 
    npm init -y # 直接生成
    
    # yarn
    yarn init
    yarn init -y # 直接生成
    
    # pnpm
    pnpm init 
    pnpm init -y # 直接生成
    
    
  3. 具体应用程序安装依赖

    # 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>
    
  4. 移除项目中的依赖

    # npm
    npm uninstall <pacakge_name>
    
    # yarn
    yarn remove <package_name>
    
    # pnpm 
    pnpm remove <package_name>
    
  5. 更新项目中的依赖包

    # npm
    npm update <pacakge_name>
    
    # yarn
    yarn upgrade <package_name>
    
    #pnpm
    pnpm update <package_name>
    
  6. 安装项目的全部依赖

    # npm 
    npm install
    
    # yarn
    yarn install 或 yarn
    
    # pnpm
    pnpm install
    
  7. 查看项目依赖列表

    # 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  # 查看根依赖项
    
  8. 查看全局安装包

    # 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
    
  9. 查看全局安装包位置

    # 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
    
  10. 检验过时的包

    # 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
    
  11. 运行安全审计

    # npm 
    npm audit
    
    # yarn
    yarn audit
    
    # pnpm
    pnpm audit
    
  12. 让你当前目录下的软件包在系统范围内或其他位置都可访问

    # 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
    
  13. 发布/登录/登出,一系列NPM Registry操作

    # npm
    npm publish/login/logout
    
    # yarn
    yarn publish/login/logout
    
    #pnpm 
    pnpm publish/login/logout
    

拓展

  • npx

    从本地node_modules/.bin或从中央高速缓存执行,安装运行<命令>所需的任何包。

    默认情况下,NPX将检查$path中的是否存在于本地项目二进制文件中,并执行该命令。如果找不到,则将在执行之前安装它。

参考

www.npmjs.cn/getting-sta…

www.yarnpkg.cn/

www.pnpm.cn/

nodejs.cn/learn/an-in…

最后

您的每一个点赞及评论都是对我坚持写作最大的支持! 另外希望各位朋友和我交流讨论,如有不对的地方,更希望批评指正!

我是程序员_随心,希望能够通过自己的学习输出给你带来帮助。