Yarn 2尝鲜

3,237 阅读7分钟

Yarn 2的特性

详细介绍可以参考Introducing Yarn 2 ! 。我比较关注的是workspace和PnP。

workspace

先说一下workspace,Yarn新增了一个插件,提供一个新的命令yarn workspaces foreach

但其实,早在Yarn@1,就可以执行诸如yarn workspaces run script的命令来运行monorepo的子package命令。但是,注意这个但是,如果package里没有这个script,就会报错(我就笑了)。

Yarn@2新增的这个命令,虽然长的也挺像的,但至少不再报错了,让我可以在monorepor中不再需要写一些空script或者使用lerna辅助执行脚本了。

PnP

本文主题主要是围绕着PnP展开的,那什么是PnP呢?

有一个众所周知的图:

轮子哥们决定必须要拯救这个宇宙,使用PnP可以让依赖扁平,尽最大可能性的避免重复文件,同时也可以让依赖查找更高效。

原生node的查找依赖方式是向上级目录层层递归遍历node_modules文件夹,虽然,现有的包管理版本都已经做到了依赖提升,让依赖项尽量扁平化,但当碰到包依赖版本不匹配的时候,仍然会存在嵌套目录。

而PnP,它记录了依赖的准群硬盘位置,可以在查找依赖时减少硬盘读写,同时,可以做到所有依赖项完全扁平化。

RC

关注了yarn的PnP特性有一段时间了(之前使用yarn --pnp也尝鲜过)。当它正式进入RC阶段的时候,我决定,再进行一波尝鲜,于是在2020.02,开始了一轮“大胆”试用。

ROUND 1(install)

文档永远是美好的:

yarn set version berry
yarn install

出现了第一个问题,因为有些包是公司域下的,无法fetch,原因是Yarn 2不再读取npm的配置(翅膀硬了,铁了心要各走各路)。

不过,配置了npmRegistryServer后会马上发现新问题,因为如果registry使用的是http地址,则还需要配置unsafeHttpWhitelist才能访问,当然,如果registry支持https,就没有这一步了。

ROUND 2(pnpify)

根据文档需要对相应的工具链进行pnpify,这一步没有大坑,只是比较繁琐,详细步骤见此文档

yarn dlx @yarnpkg/pnpify --sdk vscode
# 1. 在vscode中打开ts文件
# 2. 点击右下角或者使用ctrl + shift + p选择typescript版本
# 3. 使用workspace的pnp版本的typescript

这时候已经可以进行编码了,但出现了第一个阻断点——无法查看类型定义文件

虽然通过pnpify工具链,可以得到类型提示,但因为本质上现在的依赖不再是一个文件夹,而是一个zip压缩包,导致vscode无法提供预览能力。

ROUND 3(兼容模式)

PnP可能确实步子太大,扯着蛋了,但是它提供了nodeLinker: node-modules配置,来还我们一个黑洞。

这时候,又会出现第二个阻断点——npm scripts无法执行。例如:jest、eslint等等,都是无法执行的。原因是因为原本的npm执行时会将node_modules/.bin作为环境变量,但现在的Yarn@2,可执行文件在哪都找不到了。。。必须使用yarn执行scripts,导致很多npm scripts(诸如写了npm run clean && npm run build)必须改造。

结论

有如下几个阻断性的不足:

  • 无法查看类型定义文件
  • 部分npm scripts无法执行
  • 冷启动的时候依赖解析非常慢(这个原因没有深入调查) 基于上述原因,Yarn@2对大部分开发者平时的使用习惯和认知都造成了巨大的冲击,我觉得是不利于推广的,在当时那个时间点,我(墙裂)不推荐使用。

2.3.3

时间过了大半年,吃的太饱了,我又一次准备好了“毁天灭地”地冲锋。

ROUND 1(兼容模式)

首先,我在现有工程项目里试了试nodeLinker: node-modules的兼容模式。很好,也不知道是哪个版本修复的,但至少在此兼容模式下,熟悉的node_modules/.bin回来了,可以渐进式的让开发人员使用Yarn@2了。

当然这一回合也并不是如此一帆风顺的,首先,Link阶段会非常的慢。

Yarn 2把安装步骤分为Resolution、Fetch、Link三大步骤。

慢的让人不知所措,尤其是electron,由于界面毫无反馈,我都无法判断进程是否已经卡死,最终只能通过关闭built,让整个安装步骤顺利完成(又是新的配置项):

// package.json
{
  "dependenciesMeta": {
    "electron": {
      "built": false
    }
  }
}

ROUND 2(install)

有了兼容模式的保底,我就更有勇气尝试PnP了。然而,结局是,不止是Link慢了,甚至无法成功,通过日志看,似乎错误都是和fsevents仓库有关。

此处略去N分钟Google时间

结论就是使用最新版本的Yarn可破:

yarn set version from sources

还有一个比较常见的问题就是,有些包在安装时会报:tried to access XXX, but it isn't declared in its dependencies

这是因为虽然实际上很多包都是组合使用的,但理论上,每个包不应该感知其他包的存在,每个包的依赖都应该显式声明在dependencies。如果因为某些包依赖描述不严格导致报错的,可以修改如下配置修复(注意,正如其名,该配置只在PnP模式下生效):

pnpMode: loose

之后就比较顺利的完成依赖安装了。当然,因为在PnP模式下,没有bin目录了,所有scripts都需要使用yarn XX来执行。

ROUND 3(pnpify)

这一步是最平稳的,此外我还欣喜的发现了vscode插件——ZipFS,它解决了无法在vscode中查看类型定义文件的痛点。果然,轮子哥们是不会放任用户不管的。

虽然多少还是会影响之前的习惯,但总体而言,目前的开发体验没有阻断点了。

ROUND 4(library)

在正常的业务工程项目里已经可以正常使用Yarn 2了。那在三方库研发中呢?(主要涉及npm publish等操作)。

果然,该来的灾难还是来了,Yarn 2居!然!不!认!识!prepublish等hook。这导致很多现有的工作流都崩了,例如这个scripts:

"prepublishOnly": "npm run clean && npm run build && npm test"

非常的合情合理,但是现在执行yarn npm publish就会忽略这一切钩子,直接发射,显然不符合预期。但如果继续用npm publish吧,因为没有bin目录,还是崩了。

左右为难,左思右想,虽然也不是完全没机会,但得写个折中的发布命令:

"pub": "yarn clean && yarn build && yarn test && yarn npm publish --ignore-scripts"

我容易吗我。。。

结论

相较于二月份的RC版本,解决了无法查看类型定义文件的痛点,但目前,在scripts的使用中侵入还比较多,无法做到完全无感的兼容原有习惯。而且,安装过程中其实不止是fsevents,还有不少库(storybookjs的很多库似乎都会报错)也都需要各种Google解决甚至目前还是不兼容状态。

所以,即使在当下这个时间点,在部分业务项目中可以酌情启用Yarn 2来拯救硬盘空间,省出来的空间可以存一些珍贵的影视资料。但一言以蔽之,目前,我依然还是不推荐使用。

其他感想

Zero Install

yarn@2的理念是将依赖和可执行文件都托管在仓库中,这样就能做到项目开发的快速启动。但我内心一直有点纠结,总感觉这就像是把node_modules托管在git上一样。。。所以,目前的项目中我都是使用enableGlobalCache: true将依赖外置于项目。

但诸如yarn releases(可执行文件)、yarn plugins(诸如workspaces)插件,我虽然也不是很倾向于托管。但可以勉强接受,一是这部分文件更新频率不高,二是确实有时候需要使用某个开发版本的yarn可执行文件,才能完成项目依赖安装,使用git托管可以避免协作人员重复踩坑。

可取之处?

Q:这么多灾难下,yarn@2有什么香的地方吗?

A:没啥特别的。除了可以装X,哦不,证明自己吃饱了,另外还可以省点磁盘空间。