1. 什么是Plug'n'Play?
Yarn Plug'n'Play (通常称为 Yarn PnP)在最新的yarn的发布版本中作为默认的安装策略。也可以转换成其他很多中传统的安装方式(比如 node_modules 安装方式或者 pnpm 符号链接的方式),但是Yarn PnP做了很多的提升和优化,所以我们更推荐在创建新的项目时能够使用Yarn PnP。
2. Yarn PnP的工作原理
对于第一次使用PnP的开发者来说,查看项目中的文件,可能会发现到没有node_modules文件夹。不要惊讶,这也不是安装失败,这是PnP最大的亮点和特色。
node_modules 被干掉了!!!
根据Yarn PnP的工作方式,它告诉Yarn生成一个Node.js加载程序文件来代替原来的Node_modules文件夹。新增的加载文件名为.pnp.js,包含有关项目依赖关系树的所有信息,工具根据文件指导包在磁盘上的位置,并让知道如何解决require和import调用。
3. Yarn PnP 的优势
PnP解决了各种问题。可以通过更智能的节点模块布局算法来解决其中的一些问题(这也是pnpm风格的符号链接一直在试图做到的一点),但是PnP是唯一可以解决所有问题的策略:
3.1 极简的安装逻辑
Yarn PnP安装通常做一件事:生成Node.js加载程序文件(.PnP.cjs)。在其他包管理器中,很大一部分时间用于执行I/O操作,将文件从一个位置复制到另一个位置,无论是在像npm这样的磁盘上,还是通过像pnpm这样的符号链接/硬链接。
3.2 跨磁盘共享安装
与前一点相关,Yarn PnP允许在磁盘上的所有项目中复用相同的包依赖。但是与pnpm不同,pnpm使用内容可寻址存储,每个包中的每个文件都需要硬链接到其最终目的地,PnP加载程序通过包的缓存路径直接引用包,从而消除了很多复杂性。
3.3 完美且正确的依赖提升
典型的 node_modulues 模块安装试图通过提升包来优化 最终的 node_modules 的大小,但代价是幽灵依赖性的风险更高。同时,即使是这些优化也有局限性!一些依赖模式阻止了安全提升,导致包重复和多次实例化。
3.4 幽灵依赖守卫
因为Yarn保留了所有包及其依赖项的列表,所以它可以防止在解析过程中对未说明的依赖项的访问,使您能够在这些问题深入到代码库并在部署时危害应用程序的稳定性之前快速识别和修复这些问题。
这有时也被认为是采用 yarn PnP的一个挑战。因为当其他包管理器似乎开箱即用时,yarn pnp 可能会报告错误,如果使用的不规范时,添加、升级或删除不相关的依赖项时开始出现奇怪的损坏。
虽然它确实增加了一些使用的成本,但它是使Yarn成为一个非常稳定的包管理器的关键部分。一个今天有效的应用程序在未来不会突然中断,在您的PR合并后很长一段时间,您的同事也不会面临看似随机的问题。
3.5 详细的报错
当使用Node.js导入或require调用无效时,你只会得到一个一般性的错误,它并不能真正告诉你问题出在哪里或如何解决:
Uncaught Error: Cannot find module 'not-found'
PnP不仅告诉您问题是什么,还告诉您涉及哪些包装。例如,根据具体情况,可能会发出以下两条错误消息::
// example1
Error: Your application tried to access not-found, but it isn't declared in your dependencies; this makes the require call ambiguous and unsound.
Required package: not-found
Required by: /path/to/my-project/
// example2
Error: awesome-plugin tried to access awesome-core (a peer dependency) but it isn't provided by its ancestors; this makes the require call ambiguous and unsound.
Required package: awesome-plugin
Required by: awesome-core
Ancestor breaking the chain: awesome-template
详细的语义错误提示在很大程度上帮助您理解和解决由依赖关系引起的问题。
4. 使用成本高吗?
4.1 创建一个新的项目,
你的项目本身应该几乎“开箱即用”。您可能不得不不时地使用packageExtensions 来修复偶尔出现的幽灵依赖关系,但这仍然很少见,而且这个过程在其他方面很简单。生态系统中的大多数工具都经过设计和测试,可以在Yarn PnP环境中正常工作,因此很少出现问题。
有一个需要注意的是
React Native/Expo, 这个要求必须使用node_modules安装.
实际上,更主要问题是IDE集成。所有IDE都对Yarn PnP有一定程度的支持,但总的来说,您应该遵循本指南中的步骤,以确保所有导入都得到正确解决。
4.2 存在的项目迁移
在过去由yarn Classic安装的项目中运行 yarn install 将导致 yarn PnP 自动禁用,以使迁移更顺利。将受益于现代版本中实现的增强的稳定性和其他功能,并可以决定以后是否花时间迁移到PnP。
现有项目可能更难迁移到Yarn PnP,原因如下:
- 项目中如果有了很多依赖,因此可能会有更多的包列出幽灵依赖项
- 它们可能被锁定在各自包的旧版本上,因此有更高的机会包含幽灵依赖项
- 您自己的脚本可能会无意中依赖于一些实现细节或幽灵依赖关系,有时甚至在您没有意识到的情况下
这些都不是阻止使用PnP的理由,但它们意味着将现有项目迁移到Yarn PnP可能需要几天时间。然而提供了一些工具来简化这一过程,看看下面的足迹可以帮助您更快地确定是什么方式导致了一些东西断裂,所以这也不是不可能的。
请记住,迁移到Yarn PnP是可选的:您可以通过在项目的.yarnrc.yml文件中设置nodeLinker:node-modules设置,随时恢复到node_modules安装。
5. Footguns
5.1 对等依赖关系
对等依赖关系很强大,但很难实现——对于非PnP项目来说更是如此,它们必须在文件系统层次结构允许的范围内工作。
另一方面,Yarn PnP没有这个限制,它将准确地表示依赖树中每个项目的对等依赖关系,甚至是工作区。如果一个工作区具有对等依赖关系,并且该依赖关系由不同的版本根据其祖父母来实现,则该工作区将实例化两次,每个唯一的“依赖集”实例化一次。
这是正确的行为,但如果您的项目大量使用对等依赖关系,而不确保它们总是由完全相同的版本来实现,则可能会导致实例化工作区的数量意外激增。
5.2 共享二进制文件
Yarn可以防止项目所依赖的包中的幽灵依赖,也可以防止您自己的代码本身的幽灵依赖——这是为了减少包在开发机器上工作但一旦发布就中断的可能性。
然而,当涉及到 bins 时,它也有副作用。如果您在项目的根目录中列出了typescript,tsc二进制文件将在根包中可用,但仅在根项目中可用。换句话说,任何在脚本中使用tsc二进制文件的工作区都需要在其依赖项中声明它。
避免这类问题的一个好建议是有一个“工具”工作区来包含您的基础设施工具和脚本,并让所有其他工作区都依赖于它。
6. 常见问题
6.1 与npm/pnpm的兼容性
Yarn PnP被设计为使用与其他包管理器完全相同的“公共接口”,不同之处在于已经存在的实现细节。如果一个项目使用Yarn PnP,它应该在任何地方都能工作!
但有一点需要注意:事实并非总是如此。由于其他包管理器不/不能强制执行依赖项的正确列表,因此它们更容易意外地将重影依赖项发送给消费者。这样,使用Yarn PnP可以被视为对生态系统健康的良好实践!🙂
6.2 如何修复重影依赖关系?
Ghost依赖项可以使用packageExtensions设置来解决,该设置允许您向依赖项树中的任何包添加新的依赖项。例如,如果您面临一个错误,例如@babel/core试图访问@babel/types,但它没有在其依赖项中声明,您可以通过在.yarnrc.yml文件中添加以下内容来轻松修复它:
packageExtensions:
“@babel/core@ *”:
dependencies:
“@babel/types”:“* ”
有时扩展peerDependencies字段而不是dependencies域是有意义的,这将根据具体情况进行处理。
为了避免添加过多的packageExtensions条目,Yarn团队在我们自动修复的生态系统中维护了一个已知的ghost依赖项列表