使用pnpm包管理器替代npm及yarn的命令示例

前言

今天给大家介绍一种新的包管理器:pnpm,pnpm 由 zkochan 开发,最初也是在用 npm 或者 yarn 遇到了一些不爽的地方,于是自己做了一个开源的包管理器,也为优化包管理提供了一种不同的新思路,接下来我们就看一下为何要使用 pnpm,以及他能做什么

值得一提的是,zkochan 也是一名乌克兰的开发者,他也在主页呼吁大家援助一下乌克兰,我也希望当前的国际局势能更快的稳定下来,不要损失这么优秀的开发者

为什么会有 pnpm

一句话概括就是:节约磁盘空间并提升安装速度

设想一下,假如公司的每个项目都用了 vue 全家桶,那么每个项目都需要安装 vue、vue-router、vuex、axios 等几乎相同的库,如果有 100 个项目,那就要重复安装 100 遍!!,要知道,现如今,硬盘虽然便宜,但也没那么多插槽啊,更别说笔记本电脑这寸土寸金的资源空间。

因此,pnpm 的核心就是将所有的包都存放在一个资源仓库中,而每个项目的 node_modules 通过软链接的形式将包链接到资源仓库中,这样不仅节省了空间,同时也加快了安装速度

不止于此

pnpm 的另一个优点,在保留了非扁平化的 node_modules 文件夹,同时节省了空间

什么是扁平化的 node_modules

我们简单回顾一下 npm 的发展历史

1.最初,npm 只是简单的通过依赖去递归的安装包,所有的依赖放在相应的包文件夹下面,假如 A 依赖了 foo,B 也依赖了 foo,那么就会有两份 foo 安装在 node_modules

node_modules
├── A
│   └── node_modules
│       └── foo
└── B
    └── node_modules
        └── foo

2.经过一次优化, npm 为了节省空间,采用了扁平化的 node_modules,这样,假如 A 和 B 都依赖了 foo,那么 foo 会提升至顶层,也就是说 node_modules 文件夹里的包会变成 A、B、foo

node_modules
├── A
├── B  
└── foo

这样的好处是,同一个包 foo 只会有一份,节省了空间,但是引入了一个新的问题,引用混乱!!。

试想一下,我们的项目依赖本来只有 A 和 B,假如你想 import foo,肯定是找不到的,但是扁平化的结果,将 A 和 B 的依赖层级提升到了顶级,这样,我们直接引用 foo,其实也不会报错,但实际上我们并没有指定依赖 foo,导致了使用上的混乱,假如有一天,A 和 B 都不依赖于 foo 了,那我们的项目或者说我提供的包就会报错

这个不良的行为其实已经默默的融入我们的开发中,当我们使用 ant-design 时,我们可以直接使用 moment,当我们使用 axios 时,我们可以直接使用 qs,当我们使用 webpack 时,我们可以直接使用 lodash,而无需额外的安装包

pnpm 的 node_modules

假设我们的项目依赖 foo 包,而 foo 又依赖 bar 包,那么 pnpm 安装后的实际 node_modules 结果如下

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       └── bar -> <store>/bar
    └── foo@1.0.0
        └── node_modules
            ├── foo -> <store>/foo
            └── bar -> ../../bar@1.0.0/node_modules/bar

我们分析一下这个目录结构

  • 包都是从 store 软链接而来
  • 项目直接使用的包只有一个 foo,保证了只有依赖项中的包才能访问
  • 有个隐藏的文件夹 .pnpm,这里将所有的依赖进行扁平化,这样避免了循环符号链接,同时 foo 还能引用自己

如果添加 qar@2.0.0 作为 bar 和 foo 的依赖项,那么结果会变成

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
    ├── bar@1.0.0
    │   └── node_modules
    │       ├── bar -> <store>/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
    ├── foo@1.0.0
    │   └── node_modules
    │       ├── foo -> <store>/foo
    │       ├── bar -> ../../bar@1.0.0/node_modules/bar
    │       └── qar -> ../../qar@2.0.0/node_modules/qar
    └── qar@2.0.0
        └── node_modules
            └── qar -> <store>/qar

如何使用 pnpm

安装

直接安装

curl -fsSL https://get.pnpm.io/install.sh | PNPM_VERSION=7.0.0-rc.2 sh -

或者

wget -qO- https://get.pnpm.io/install.sh | PNPM_VERSION=7.0.0-rc.2 sh -

通过 npm 安装

npm install -g pnpm@next-7

通过 HomeBrew 安装( 一种 MacOS 包管理器)

brew install pnpm

升级

简单的通过 pnpm 自己的命令升级即可

pnpm add -g pnpm

使用

配置

大部分配置可以直接使用 npm 的配置,与之区别的是有些配置可能是 pnpm 专有的,例如:

# 配置仓库路径
pnpm config set store-dir /path/to/.pnpm-store

命令

安装依赖

pnpm install
# 其他一些参数
--offline   仅从 store 中离线下载
--dev, -D   只下载 devDependencies 依赖
--frozen-lockfile  不会生成 lockfile
--shamefully-hoist 创建扁平结构,类似于 npm ,不推荐

2. 增加依赖

pnpm add <pkg>

这里 pnpm 有多种增加方式

  • 直接通过配置的源安装

在 workspace 中,会先检查 workspace 是否有相应包,至于 workspace 是什么,在下面会详细说道

从本地安装: 例如你想调试开发的包时

pnpm add ./package.tar.gz
pnpm add ./some-directory

从远端安装 Tar 包

pnpm add https://github.com/indexzero/forever/tarball/v0.5.6

从 git 安装

pnpm add <git remote url>
# 其他一些参数
--save-dev, -D	安装为 devDependencies,也就是开发环境使用的包
--save-peer  	安装为 peerDependencies ,同时安装到 devDependencies, peerDependencies 通常是和该包一起使用的其他包,需要使用者同时下载
--global, -g	安装为全局依赖

发布依赖

pnpm [-r] publish [&lt;tarball|folder&gt;] [--tag &lt;tag&gt;]
     [--access &lt;public|restricted&gt;] [options]
--no-git-checks	不检查当前分支是否是可发布分支,是否有没有提交的代码,直接发布
--dry-run	执行发布包的所有流程,但唯独不会真正把包发布到源上,这个在测试发布的时候非常有用
-r		递归的执行发布命令,通常在 workspace 中使用

这里只列出常用的一些,更多的命令可以参考官网

和其他包管理器比较

npm 命令 pnpm 等效
npm install pnpm install
npm i [pnpm add ]
npm run [pnpm ]
功能 pnpm Yarn npm
工作空间支持(monorepo)
隔离的 node_modules ️ – 默认
提升的 node_modules ️ – 默认
Plug’n’Play ️ – 默认
零安装
修补依赖项
管理 Node.js 版本
有锁文件 ️ – pnpm-lock.yaml ️ – yarn.lock ️ – package-lock.json
支持覆盖 ️ – 通过 resolutions
内容可寻址存储
动态包执行 ️ – 通过 pnpm dlx ️ – 通过 yarn dlx ️ – 通过 npx

Monorepo 及 工作空间(Workspace)

什么是 Monorepo

Monorepo 由两个单词组成,Mono 指的是单个(single),repo 指的是项目存储(Repository),合起来意思就是说,大量的项目存储在单一的库中,用人话讲就是,不同的项目都写在一个 git 库里,第一次听到这个概念的时候大家肯定觉得这人指定有什么大病,这么多项目放一起,大家在里面各种修改,一个人项目修改了,另一个不相干的项目肯定还得更新代码,这不是技术倒退么,这个其实还真就是 Monorepo 的缺点,但是静下心来考虑,他的优点也还是很多的,例如:

  • 不同项目之间,如果需要复用相同的组件或者方法,现在就可以很方便的引用了,同时,这些公用方法一旦修改了,其他项目的相应逻辑交互也都更新了,不同在担心忘了安装或者更新,同时也比单纯的 复制黏贴 要更科学

这里插一句,很多公司的项目说是独立的,其实都是从已有的项目中各种复制来的,连带着以前的 bug 也都有了,还不如放一起呢

  • 也不是所有的项目都适合放在一起,只有项目之间重合度高的,才应该放一起,例如:管理系统,基本表单操作之类的复用度很高,如果是不相关的项目,彼此没有交集,那就别折腾了
  • 不同项目之间,如果拆分的力度比较细,互相之间有调用,那就适合放在一起,最常见的是 npm 库,不同的库之间可能有依赖,同时每个库还需要单独发布
  • 它倡导了一种开放,透明,共享的组织文化,所有的开发者都可以浏览学习其他人的代码

说完了 Monorepo ,离真正应用还是有点差距,多个项目之间需要保证自己的独立性,还需要能方便的根据其他项目的变动做出相应的改变,目前市面上也有很多其他的方便管理 Monorepo 的工具,例如:lernajs,幸运的是,pnpm 也对 Monorepo 有着良好的支持,pnpm 称他为 workspace

值得一提的是,vite 也对 Monorepo 做了支持,这使得当引用的其他库发生变化时,正在使用 vite dev 开发的项目也会热更新,详细可见Monorepo 和链接依赖

Workspace

pnpm 可以创建一个 workspace 将多个项目合并到一个中,一个 workspace 的根目录下必须有 pnpm-workspace.yaml 文件,常见的 pnpm-workspace.yaml 类似于下面这种配置,可以定义哪些文件或者文件夹应该包含于 workspace 中

packages:
  # all packages in subdirs of packages/ and components/
  - 'packages/**'
  - 'components/**'
  # exclude packages that are inside test directories
  - '!**/test/**'

在 workspace 根目录使用 pnpm install 命令时,会自动将 workspace 中的所有项目执行 pnpm install

workspace: 协议

pnpm 支持协议 workspace:,当使用这个协议时,pnpm 只会解析本地 workspace 中的包,workspace: 协议的使用方法和 package.json 引用包的方法一致

"foo": "workspace:*"
"foo": "workspace:../foo"
"foo": "workspace:~",
"foo": "workspace:^",
"zoo": "workspace:^1.5.0"

当需要发布包时,例如使用 pnpm pack 或者 pnpm publish 命令时,将动态的把 workspace: 协议替换为真正的版本号

结语

pnpm 目前被越来越多的人使用,其中也包括 vue 这样的一线开源框架,具体可以参见 使用示例

在诸如 pnpm 或者 yarn 这样的开源项目影响下,npm 也在反思自己,也及时的做出了更新,例如:支持 workspace 等功能,希望这样的良性循环也让 js 社区越来越好,或许有一天我还会重投 npm 的怀抱也说不定

以上就是使用pnpm包管理器替代npm及yarn的命令示例的详细内容,更多关于pnpm包管理器替代npm及yarn命令的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:

  • 一文带你了解前端包管理工具npm、yarn和pnpm
  • pnpm的安装和使用指南(推荐!)
  • Yarn与Lerna管理monorepo使用详解
  • Vue3.0中的monorepo管理模式的实现
  • JS前端架构pnpm构建Monorepo方式管理demo
张贴在2