pnpm和yarn2 pnp的对比

工作中我们会遇到同时支撑多个vue项目,而且是频繁修改多个项目,我们本地会有clone多个项目并且每个项目有自己的node_modules,导致我们的电脑磁盘爆满,偶尔删一下空出来磁盘空间,过两天又要修改刚删过的文件不得不安装过来;苦–!

找问题的话 就发现我们每个项目去掉node_modules文件以及.git 或者一些其他的依赖后 项目本身源文件大小也就20M,但是加上这些依赖后我们的项目会有500M-600M

再接着找问题的话发现和使用npm或者yarn时的安装的依赖有很大关系,如果你有100个项目使用了某个依赖,就会有100份依赖的副本保存在磁盘上,即使我们删除了部分项目的依赖,只要我们重新安装,我们清除的磁盘就又爆满了;(对于pnpm,依赖项将存储在一个内容可寻址的仓库中上,实现了节约磁盘空间并提升安装速度)

除此之外, Node 的模块机制还有以下缺点:

  • Node 本身并没有模块的概念, 它在运行时进行查找和加载。你可能在开发环境运行得好好的, 可能到了线上就运行不了了, 原因是一个模块没有添加到 package.json

  • Node 模块的查找策略非常浪费.(递归遍历所有的项目依赖关系) 这个缺点在大部分前端项目中可以进行优化,比如 webpack 就可以限定只在项目根目录下的 node_modules 中查找, 但是对于嵌套的依赖, 依然需要 2 次以上的查找

  • node_modules 不能有效地处理重复的包. 两个名称相同但是不同版本的包是不能在一个目录下共存的.

# ① 假设项目依赖a,b,c三个模块, 依赖树为:
├─── a
│    ├── vue@3
├─── b
│    ├──vue@2
├─── c
     ├──vue@2
# yarn安装时会按照项目被依赖的次数作为权重, 将依赖提升(hoisting),
# 安装后的node_modules结构为:
  .
  └── node_modules
      ├── a
      │   ├── index.js
      │   ├── node_modules
      │   │   └── vue  # @3
      │   └── package.json
      ├── b
      │   ├── index.js
      │   └── package.json
      ├── c
      │   ├── index.js
      │   └── package.json
      └── vue  # @2 被依赖了两次, 所以进行提升

# ② 现在假设在①的基础上, 根项目依赖了react@15, 对于项目自己的依赖肯定是要放在node_modules根目录的,
# 由于一个目录下不能存在同名目录, 所以react@16没有的提升机会. 
# 安装后node_moduels结构为
  └── node_modules
      ├── a
      │   ├── index.js
      │   └── package.json # vue@3 提升
      ├── b
      │   ├── index.js
      │   ├── node_modules
      │   │   └── vue  # @2
      │   └── package.json
      ├── c
      │   ├── index.js
      │   ├── node_modules
      │   │   └── vue  # @2
      │   └── package.json
      └── vue  # @3
# 上面的结果可以看出, vue@2出现了重复

那么,有没有什么好的方式解决吗?
这里会主要推出以下两位包管理器:

  • yarn2 pnp
  • pnpm快速的,节省磁盘空间的包管理工具

pnpm

Pnpm 是node.js的包管理工具,然而并不是npm或者yarn的优化兼容模式,通过对node_modules的处理,让模块的管理更加快速和高效。

  • Pnpm 运行起来非常的快,超过了npm和yarn是同类工具速度的将近 2 倍
  • node_modules 中的所有文件均链接自单一存储位置
  • Pnpm 内置了对单个源码仓库中包含多个软件包的支持
  • Pnpm 创建的 node_modules 并非扁平结构,因此代码不能对任意软件包进行访问
  • pnpm pnpm采用了一种巧妙的方法,利用硬链接和符号链接来避免复制所有本地缓存源文件,这是yarn的最大的性能弱点之一,内部使用基于内容寻址的文件系统来存储磁盘上所有的文件

下面会讲一下pnpm解决npm和yarn的现有存在问题,以及是如何节省磁盘空间和提升安装速度的。

vue 项目使用pnpm

Vue cli已经默认添加了对pnpm的支持,所以项目切换pnpm的方式可以直接切换,支持vue2 vue3:

 npm i -g pnpm
 rm -rf node_modules
 rm -rf yarn.lock
 
 # 安装依赖 第一次安装会耗费一点时间
 pnpm install
 
 # 运行服务
 pnpm run serve

可以看到pnpm方式安装的依赖,可以看到如下变化:

  • 保留了node_modules文件,避免项目破坏性升级,向下兼容友好,
  • 根目录下的node_modules下面不再是眼花缭乱的依赖,而是跟 package.json 声明的依赖基本保持一致
  • 添加了pnpm-lock.yaml文件存放着所有依赖关系图
  • node_modules下主要为项目声明主依赖,在node_modules/.pnpm文件夹下则扁平放着所有项目所有依赖软连,这样的好处可能对于pnpm来说查找更容易
  • 将包本身和依赖放在同一个node_module下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙

常用命令

pnpm install xxx # 安装包
pnpm update xxx # 更新包
pnpm uninstall xxx # 卸载包

查看更多

yarn2 pnp

Yarn 2 最大的特点是它的 Plug’n’Play 系统(以下简称 PnP)。虽然 Yarn 1 也可以使用 PnP,但是 Yarn 2 默认开启了它。
Pnp的产生也是因为node_modules,它是社区尝试替代node_modules的一个方案。在第一次安装依赖后,除了yarn.lock,Yarn 2 还会创建一个.pnp.js文件。通过这个文件,Yarn 可以主动地告诉 Node.js 应当在哪个具体的位置找到某一个包,而不是让 Node.js 自己尝试在 node_modules 目录中寻找依赖。通过这种方式,Yarn 可以把所有的包以 zip 的格式储存起来,甚至让不同的工程共用同一份依赖文件,从而减少依赖的体积和加载时间。

然而,目前的 Node.js 社区工具几乎都是基于 node_modules 进行开发的。这导致 Yarn PnP 和目前的很多工具都不兼容,即使webpack、rollup等的支持,仍然没有让yarn pnp普及起来。

安装使用

初始化之前,项目下吧。yarnrc.yml .yarnrc删除

  1. 切换到1x版本
    yarn set version latest
> output
Downloading https://github.com/yarnpkg/yarn/releases/download/v1.22.10/yarn-1.22.10.js...
Saving it into /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarn/releases/yarn-1.22.10.cjs...
Updating /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarnrc...
Done!
  1. 切换到2x版本
    yarn set version berry
> output
Resolving berry to a url...
Downloading https://github.com/yarnpkg/berry/raw/master/packages/berry-cli/bin/berry.js...
Saving it into /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarn/releases/yarn-berry.cjs...
Updating /Users/xiaoshao/Downloads/ROOT/pnpm/vue3-test-pnp/.yarnrc.yml...
Done!

在yarnrc.yml 添加如下配置:

npmAlwaysAuth: true

npmAuthToken: 7BPLCVtdNJLsH7vJT5Cm9g==

npmRegistryServer: "http://192.168.x.x:9999/"

unsafeHttpWhitelist:
  - 192.168.1.1
yarnPath: .yarn/releases/yarn-berry.cjs
enableGlobalCache: true

yarn install 开始安装依赖

查看配置

yarn config

查看目录结构

.yarn 目录结构
├── releases
│   └── yarn-berry.js
│—————— .yarnrc.yml

工程根目录下会新增一个.yarn目录及一个.yarnrc.yml的配置文件:

  • .yarn目录:存放Yarn 2的具体执行文件、由Yarn 2安装的依赖等
  • .yarnrc.yml配置文件:此工程针对Yarn 2的具体配置文件,和.npmrc、.yarnrc功能类似,这里要注意Yarn 2不会再读取.npmrc、.yarnrc中的配置,同时文件扩展也必须是yml,.yarnrc不能生效

.yarn和.yarnrc.yml都应该提交到仓库

vue项目依赖报错解决

默认情况下完成上述操作后,webpack构建相关项目无法正常运行,需按照下面配置

yarn install pnp-webpack-plugin -D
// vue.config.js
const PnpWebpackPlugin = require('pnp-webpack-plugin')
module.exports = {
  configureWebpack: {
    resolve: {
      plugins: [PnpWebpackPlugin],
    },
    resolveLoader: {
      plugins: [PnpWebpackPlugin.moduleLoader(module)]
    }
  }
}

yarn2 pnp 和node_modules切换

# .yarnrc.yml
yarnPath: ".yarn/releases/yarn-berry.js"

# 如果工程跑不起来可以先尝试增加下面的配置:
nodeLinker: "pnp"
pnpMode: "loose"

# 如果仍然跑不起来,可以用下面的配置完全按照以前的依赖按照方式
nodeLinker: "node-modules"

需要讨论项

因为Yarn 2通过将所有npm包以zip包的形式存在store中,以zip包的方式管理依赖,大大减少了文件数量和文件体积,故而使得工程的依赖能够进行版本管理,进而能够完美地实现依赖锁定。

基于上述场景,部分人提出将工程依赖都提交到仓库中了,协作者拉取代码后甚至都不再需要执行依赖安装的步骤。

上面的论题,只能说有一定的好处,但是个人观点更觉得弊大于利。

杂项

Yarn 1 的一个很大的问题是yarn.lock中会出现依赖版本的重复和落后的问题。这个问题曾经造成了一些令人非常疑惑的现象。Yarn 2 原生支持了yarn deque命令,可以说从根本上解决了这个问题。

总结

同一份代码分别在yarn/yarn2 pnp/pnpm下安装依赖后的大小查看,结果应该很明显

  • yarn2 pnp 不降反升了体积
  • pnpm体积确实缩小了一点点,就跟你120斤瘦了2斤的感觉一样,就是稍微轻了一点点

整体使用感受下来,

  • pnpm 优点:确实更容易简单一些,报错少,入门低,且对项目兼容性也够好,确实也是有一定的效果;缺点:在安装层面没有优化,每次都是需要重新下载安装依赖,所以安装速度变化没有太大
  • yarn2 pnp 优点:通过全局store能在安装速度上得到提升,毕竟使用了本地缓存;缺点:在文件目录结构node_modules上暂时没有看到很大的优化,体积在一定程度上没有降低,可能还会增加体积

总结来说:确实能感受到Yarn pnp在处理的精妙之处,目前仍是觉得稍微不稳定,但是感觉仍需要几个小版本升级可能才会真的普及起来;而pnpm已经发展了很多年,也确实在实用性、稳定性上有一定的作用。

参考资料

  • 为什么推荐使用pnpm
  • npm、yarn、cnpm全面解析
  • Yarn 2的安装与使用

你可能感兴趣的:(前端,pnpm,npm,yarn,包管理器)