Yarn 的 Plug'n'Play 特性

前言

Yarn 团队在春节前公布了 Yarn 2.0 的规划。其中提到了一个以前没据说过的名词 “PnP”。发现 Yarn 的这个功能早在 18 年 9 月份就被提出实现了。因而花了一些时间了解了一下它的工做原理以及解决的问题并整理除了本篇文章。html

现状与痛点

Yarn 团队开发 PnP 特性最直接的缘由就是现有的依赖管理方式效率过低。引用依赖时慢,安装依赖时也慢。node

先说说 Node 在处理依赖引用时的逻辑,这个流程会有以下两种状况:react

  • 若是咱们传给 require() 调用的参数是一个核心模块(例如 "fs"、"path"等)或者是一个本地相对路径(例如 ./module-a.js/my-li/module-b.js),那么 Node 会直接使用对应的文件。
  • 若是不是前面描述的状况,那么 Node 会开始寻找一个名为 node_modules 的目录:
    1. 首先 Node 会在当前目录寻找 node_modules,若是没有则到父目录查找,以此类推直到系统根目录。
    2. 找到 node_modules 目录以后,再在该目录中寻找名为 moduleName.js 的文件或是名为 moduleName 的子目录。

此处旨在说明问题,对 Node 内部模块解析逻辑作了简化描述git

可见 Node 在解析依赖时须要进行大量的文件 I/O 操做,效率并不高。github

再来看看安装依赖时发生了什么,现阶段 yarn install 操做会执行如下 4 个步骤:npm

  1. 将依赖包的版本区间解析为某个具体的版本号
  2. 下载对应版本依赖的 tar 包到本地离线镜像
  3. 将依赖从离线镜像解压到本地缓存
  4. 将依赖从缓存拷贝到当前目录的 node_modules 目录

其中第 4 步一样涉及大量的文件 I/O,致使安装依赖时效率不高(尤为是在 CI 环境,每次都须要安装所有依赖)。json

Facebook 的工程师受够了这些问题决定寻找一个能完全解决问题同时还能够与现有生态兼容的解决方案。这即是 Plug'n'Play 特性,简称 PnP。它已在 Facebook 内部测试了一段时间,如今 Yarn 团队决定与社区分享并共同优化该方案。api

实现方案

PnP 的具体工做原理是,做为把依赖从缓存拷贝到 node_modules 的替代方案,Yarn 会维护一张静态映射表,该表中包含了如下信息:缓存

  • 当前依赖树中包含了哪些依赖包的哪些版本
  • 这些依赖包是如何互相关联的
  • 这些依赖包在文件系统中的具体位置

这个映射表在 Yarn 的 PnP 实现中对应项目目录中的 .pnp.js 文件。bash

这个 .pnp.js 文件是如何生成,Yarn 又是如何利用它的呢?

在安装依赖时,在第 3 步完成以后,Yarn 并不会拷贝依赖到 node_modules 目录,而是会在 .pnp.js 中记录下该依赖在缓存中的具体位置。这样就避免了大量的 I/O 操做同时项目目录也不会有 node_modules 目录生成。

同时 .pnp.js 还包含了一个特殊的 resolver,Yarn 会利用这个特殊的 resolver 来处理 require() 请求,该 resolver 会根据 .pnp.js 文件中包含的静态映射表直接肯定依赖在文件系统中的具体位置,从而避免了现有实如今处理依赖引用时的 I/O 操做。

带来了哪些好处

从 PnP 的实现方案能够看出,同一个系统上不一样项目引用的相同依赖的相同版本实际都是指向的缓存中的同一个目录。这带来了几个最直观的好处:

  • 安装依赖的速度获得了空前的提高
  • CI 环境中多个 CI 实例能够共享同一份缓存
  • 同一个系统中的多个项目再也不须要占用多份磁盘空间

如何开始使用 Plug'n'Play 特性?

首先你须要 Yarn 1.12+ 版本。而后根据你的具体场景能够选择:

使用 create-react-app 建立项目时开启 PnP

create-react-app 已经集成了对 PnP 的支持。只需在建立项目时添加 --use-pnp 参数便可。

npx create-react-app testapp --use-pnp 
复制代码

在已有项目中开启 PnP

只需在项目中执行:

yarn --pnp
复制代码

便可开启 PnP 特性。

注意事项

pkg.installConfig 字段

在项目中开启 PnP 特性后,Yarn 会在 package.json 文件中建立一个 installConfig 字段:

{
  "installConfig": {
    "pnp": true
  }
}
复制代码

只要 installConfig.pnp 的值是一个真值且当前版本的 Yarn 支持,PnP 特性就会被启用。

执行 npm script 或是运行 .js 文件

因为在开启了 PnP 的项目中再也不有 node_modules 目录,全部的依赖引用都必须由 .pnp.js 中的 resolver 处理。所以不管是执行 script 仍是用 node 直接执行一个 JS 文件,都必须经由 Yarn 处理。必须经过 yarn run 或是 yarn node 执行。

在项目中调试依赖

在开发过程当中咱们有时会直接修改 node_modules 目录下的依赖来调试。但在 PnP 模式下,因为依赖都指向了全局缓存,咱们再也不能够直接修改这些依赖。

针对这种场景,Yarn 提供了 yarn unplug packageName 来将某个指定依赖拷贝到项目中的 .pnp/unplugged 目录下。以后 .pnp.js 中的 resolver 就会自动加载这个 unplug 的版本。

调试完毕后,再执行 yarn unplug --clear packageName 可移除本地 .pnp/unplugged 中的对应依赖。

总结

目前 PnP 仍是一个相对比较新的特性,你们能够尝试在本地开发环境中启用 PnP 来感觉一下它带来的全新体验。遇到问题能够及时反馈到 Yarn 的 issue 列表中

参考连接:

更多文章,请关注咱们团队的公众号:全栈探索

二维码
相关文章
相关标签/搜索