pnpm包管理器替代npm及yarn命令
lain_lee 人气:9前言
今天给大家介绍一种新的包管理器: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 [<tarball|folder>] [--tag <tag>] [--access <public|restricted>] [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 的怀抱也说不定
加载全部内容