yarn yarn2 and pnpm的一些总结

之前做的组内分享,内容不说多丰富,也是精挑细选后总结的不少东西。

前言

yarn以及pnpm都是包管理工具的第三方解决方案,他们各自诞生的时代都是看到了npm本身的不足,如yarn最开始的版本针对的是npm3默认没有lockfile的问题,pnpm的作者则是对yarn没有针对node_modules进行太多优化的问题,然后通过自己的方式去解决这些不足。而yarn2(berry),以下统称berry,则是与yarn1的思路完全不同,抹去了node_modules的存在,目前还未发布正式版本。但相关文档已经占据了yarn的主域名。

接下来主要从下面三个点来讲解:

  1. CLI输出界面
  2. 依赖管理
  3. workspace

人靠衣装

先看一些主观性的东西,首当其冲就是每日一见的界面:

npm特点:core-js作者找工作的信息

yarn1,特点:emoji,步骤很清晰,简洁工整 。

pnpm特点:简单,重点突出了:重复安装包的复用问题;dependency的版本

berry特点:error code + 颜色改进

敲击

yarn又或者berry都比npm敲起来流畅点,pnpm敲起来反人类。

依赖管理

npm

npm对于依赖管理的进化史,主要是为了解决依赖树过深及很多隐藏依赖被重复安装的问题,npm3从开始的nest mode的拓扑结构,转到了flat mode的拓扑结构,一定程度上解决了重复依赖的问题。

但是这种结构也是有些问题的,首先是如果有一个隐藏依赖的两个版本都被在某个项目中被多次引用,无论哪个版本的依赖存放在root级别的目录都会带来重复引用的问题。

img

其次还有 phantom dependency 的问题,即我们显式地调用一些隐藏依赖时并没有报错的问题。因为npm和yarn的flat mode遵循的还是尽可能把依赖放在根目录。但有一种情况,devdependency里使用的隐藏依赖是不会被别的用户安装的,因此这种隐藏依赖的显式调用是在其他用户的PC里是会报错。

接着分别看一下yarn, pnpm, berry的依赖管理。

yarn

yarn的一些特性是走在npm的前边的,默认产生的lockfile就是很重要的一个。与npm5之后推出的package-lock.json不同,yarn并没有采用JSON格式的文件,而是使用了自定义的格式,名字就叫做yarn.lock,与前者不同,后者的lockfile目录结构并不能复制出完完全全一样的node_modules拓扑结构,他只是把依赖到的所有库 flat 成根目录级别,这样更方便做diff。

pnpm

但是yarn1由于采取的还是扁平化结构,与npm3后的依赖管理存在的问题相同。pnpm则不同,针对npm2的nest mode,他没有采取flat mode,而是采用了一种hard link的方式,保证了需要的版本的包只会被在一个PC上安装一次,已节省空间。

这样的做法顺理成章的解决了刚刚npm指出的两个问题,首先是重复依赖,通过hard link自然可以链接到符合要求的模块;其次隐藏依赖不会被提升到根目录,自然不会误引用

因此 phantom dependency 的问题也可以被很好的解决。

yarn2(berry)

尽管yarn1看似并没有对node_modules作出太大改动,但是他们的团队并不是没有意识到node_modules的缺憾,他们做出了Plug’n’Play的尝试,在介绍这个之前,我们先看看node_modules本身存在的一些问题。

首先node_modules本身的局限性在于解析、安装依赖时产生的大量IO操作:

  1. 解析:当require某个第三方文件时,首先在当前目录寻找node_modules,找不到再去父级,找到之后,再去这个node_modules的子目录去寻找,直到找到该文件。因为node不认识包,只认识文件,而node_moduls的设计也就注定了他不允许包管理工具正确的删除重复的包数据
  2. 安装:解析出某个具体的版本号,下载 tar 包到离线镜像,从镜像解压到本地缓存;从缓存拷贝到node_modules,即使是pnpm的hard link,也只是优化了最后一步。

因此,berry做出了修改,与其让node去查找软件包,不如直接简明扼要的告诉node应该在哪里找到这个包。Plug’n’Play特性应运而生,他其实是省略了node_modules的拷贝,转而生成了一个**.pnp.js的文件去记录包的版本,以及映射到的磁盘位置,即把每个包看作整体,压缩成一个zip;一个.yarn文件夹,里面又有cacheunplugged**目录,前者存放压缩过的依赖包,后者可以通过unplugin指令解压某个想要手动修改的包。

berry一定程度解决了一些问题:

  1. 之前介绍的npm存在的两个问题,berry因为不会生成node_modules目录,因此不存在phantom dependency的问题,同时他采用的.pnp.js的静态映射而不是copy的方式也避免了重复安装依赖的问题。

  2. 基于 .pnp.js 和 zip loading 实现的零安装,即将.pnp.js及.yarn文件夹全部上传至gitlab,在有些情况下是可行的,但是这里使用 create-react-app 进行实测,yarn 为144Mb,berry为62Mb,只是正常的压缩体积的优化;随后拿React,Vue等包做了下实验,也基本都是这个比例(7.9Mb VS 5.1Mb)(17Mb VS 8.5Mb)。

  3. 最后一点说一下一些新的特性,如插件机制,方便我们在对berry的核心代码并不熟悉的情况下开发基于berry的扩展功能,官方实现的官方实现的typescript插件,在yarn add 时自动添加@types等。

当然也存在一些问题,最明显的就是首次安装依赖的时间并没有感觉到缩短,其次还有上面所说的.yarn/cache到底要不要放到远程仓库中也是有待商榷的事。

workspace

关于workspace,yarn和pnpm很早已经推出了,主要提供了基本的monorepo支持,yarn2做了一些优化,仍是宣扬与lerna相辅相成去使用:

  1. berry为涉及到workspace的大部分指令提供了交互式的参数(- i),如yarn add -i 判断添加的某个依赖是否在其他的workspace中使用,来判断是否要复用其他workspace的版本,还是选择另外的版本。
    img

  2. berry提供了一个version插件,当一个包发布新版本时,可以交互的选择是否让其他关联包也发布新版本,只需一个交互式的命令即可。
    img

  3. Constraints,为了确保某个monorepo项目使用相同版本的依赖。或者它们不依赖于特定的程序包。或者他们使用特定类型的依赖关系,我们总需要制定一些规则来让所有的yarn workspace执行,这种规则可以通过在项目目录创建constraints.pro文件,并用相应的语法(Prolog)书写。但是因为目前constraints还在试验阶段,可能还会有一些变动(This feature is still incubating, and its exact API might change from a release to the next)。