Skip to main content

Plug'n'Play

什么是 Yarn Plug'n'Play?

¥What is Yarn Plug'n'Play?

Yarn Plug'n'Play(通常称为 Yarn PnP)是 Yarn 现代版本中的默认安装策略。它可以换成更传统的方法(包括 node_modules 安装或 pnpm 样式的基于符号链接的方法),但我们建议在创建新项目时使用它,因为它有许多改进。

¥Yarn Plug'n'Play (generally referred to as Yarn PnP) is the default installation strategy in modern releases of Yarn. It can be swapped out for more traditional approaches (including node_modules installs, or pnpm-style symlink-based approaches), but we recommend it when creating new projects due to its numerous improvements.

它是如何工作的?

¥How does it work?

如果你查看项目中的文件,你可能会注意到没有 node_modules 文件夹。这很不寻常!我们经常在 Discord 上被问到文件夹在哪里,人们认为 yarn install 默默失败了。

¥If you look into the files in your project, you may notice the absence of a node_modules folder. This is unusual! We regularly get asked on Discord where the folder is, by people thinking yarn install silently failed.

事实上,这实际上是预料之中的!Yarn PnP 的工作方式是,它告诉 Yarn 生成一个 Node.js 加载器文件来代替典型的 node_modules 文件夹。这个名为 .pnp.cjs 的加载器文件包含有关项目依赖树的所有信息,告知你的工具包在磁盘上的位置,并让它们知道如何解析 require 和 import 调用。

¥The thing is, this is actually expected! The way Yarn PnP works, it tells Yarn to generate a single Node.js loader file in place of the typical node_modules folder. This loader file, named .pnp.cjs, contains all information about your project's dependency tree, informing your tools as to the location of the packages on the disk and letting them know how to resolve require and import calls.

有什么优势?

¥What are the advantages?

Yarn PnP 解决了各种问题。可以通过更智能的 node_modules 布局算法解决其中一些问题(例如,pnpm 样式的基于符号链接的安装策略试图这样做),但 PnP 是唯一可以解决所有问题的策略:

¥Yarn PnP addresses various problems. It would be possible to address some of them via smarter node_modules layout algorithms (that's for example what the pnpm-style symlink-based install strategy attempts to do), but PnP is the only strategy that can address them all:

最小安装占用空间

¥Minimal install footprint

Yarn PnP 安装通常会做一件事:生成 Node.js 加载器文件 (.pnp.cjs)。在其他包管理器中,相当一部分时间都花在执行 I/O 操作上,将文件从一个位置复制到另一个位置,无论是在磁盘上(如 npm),还是通过符号链接/硬链接(如 pnpm)。

¥A Yarn PnP install typically does one thing: generate the Node.js loader file (.pnp.cjs). In other package managers, a significant portion of the time is spent performing I/O operations to copy files from one location to another, be it on disk like npm, or via symlinks / hardlinks like pnpm.

共享跨磁盘安装

¥Shared installs across disks

与上一点相关,Yarn PnP 允许在磁盘上的所有项目中重用相同的软件包工件。与 pnpm 不同,pnpm 使用内容可寻址存储,其中每个包中的每个文件都需要硬链接到其最终目的地,而 PnP 加载器则直接通过其缓存路径引用包,从而消除了很多复杂性。

¥Related to the previous point, Yarn PnP allows to reuse the same package artifacts across all projects on the disk. Unlike pnpm, which uses a content-addressable store where each file from each package needs to be hardlinked into its final destination, the PnP loader directly references packages via their cache path, removing a lot of complexity.

完美且正确的提升

¥Perfect and correct hoisting

典型的 node_modules 安装尝试通过提升包来优化生成的 node_modules 大小,但代价是更高的幽灵依赖风险。不幸的是,即使这些优化也有限制!一些依赖模式会阻止安全提升,导致包重复和多次实例化。

¥Typical node_modules installs attempt to optimize the resulting node_modules size by hoisting packages, at the cost of higher risks of ghost dependencies. Unfortunately, even these optimizations have limits! Some dependency patterns prevent safe hoisting, leading to package duplication and multiple instantiations.

Ghost 依赖保护

¥Ghost dependencies protection

因为 Yarn 保存了所有包及其依赖的列表,它可以防止在解析过程中访问未考虑的依赖,使你能够在这些问题深入你的代码库并危及部署时应用的稳定性之前快速识别和修复这些问题。

¥Because Yarn keeps a list of all packages and their dependencies, it can prevent accesses to dependencies unaccounted for during resolution, giving you the ability to quickly identify and fix those problems before they get deep into your codebase and jeopardize the stability of your application at deploy time.

信息

这有时被认为是采用 Yarn PnP 的挑战。这意味着当其他包管理器似乎可以开箱即用时,可能会报告错误 - 也就是说,直到你添加、升级或删除不相关的依赖时开始发生奇怪的中断。

¥This is sometimes mentioned as a challenge to adopting Yarn PnP. It means errors may be reported when other package managers would seem to work out of the box - that is, until strange breakages start happening as you add, upgrade, or remove unrelated dependencies.

虽然它确实增加了一些摩擦,但它是使 Yarn 成为非常稳定的包管理器的关键部分。今天运行的应用不会在将来突然中断,并且你的同事在你的 PR 合并后很长时间内不会遇到看似随机的问题。

¥While it does add a bit of friction, it's a critical part of what makes Yarn a very stable package manager. An application that works today won't suddenly break in the future, and your colleagues won't face seemingly random issues long after your PRs got merged.

语义错误

¥Semantic erroring

你可能从未注意到,但是当 Node.js 导入或需要调用无效时,你只会收到一个通用错误,这并没有真正告诉你问题是什么或如何解决它:

¥You may never have noticed it, but when a Node.js import or require call is invalid, you only get a generic error in return, that doesn't really tell you what's the problem or how to address it:

Uncaught Error: Cannot find module 'not-found'

Yarn PnP 不仅会告诉你问题到底是什么,还会告诉你涉及哪些软件包。例如,根据具体情况,可能会触发以下两个错误消息:

¥Yarn PnP not only tells you exactly what the problem is, but also which packages are involved. For example, the two following error messages may be emitted depending on the circumstances:

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/
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

语义错误在很大程度上让你了解和解决由依赖引起的问题。

¥Semantic erroring goes a long way into letting you understand and address problems caused by your dependencies.

使用起来难吗?

¥Is it difficult to use?

创建新项目时

¥When creating a new project

如果你从头开始创建项目,你的项目本身应该几乎可以像 "开箱即用" 一样工作。你可能不得不不时使用 packageExtensions 来修复偶尔出现的幽灵依赖,但这种情况并不常见,而且这个过程非常简单。生态系统中的大多数工具都经过设计和测试,可以在 Yarn PnP 环境中良好运行,因此很少出现问题。

¥If you're creating a project from scratch, your project itself should work almost "out of the box". You may have to use packageExtensions from time to time to fix an occasional ghost dependency, but that remains uncommon, and this process is otherwise straightforward. Most tools in the ecosystem are designed and tested to work well in Yarn PnP environments, so problems are infrequent.

危险

一个值得注意的例外是 React Native / Expo,它需要使用典型的 node_modules 安装。

¥A notable exception is React Native / Expo, which require using typical node_modules installs.

实际上,你将面临的主要问题将是围绕 IDE 集成。所有 IDE 都在一定程度上支持 Yarn PnP,但一般来说,你应该遵循本指南中的某个程序,以确保所有导入都得到正确解析。

¥Really, the main problem you will face will be around IDE integrations. All IDEs have some level of support for Yarn PnP, but in general you should expect having to follow one of the procedures from this guide to make sure all your imports are properly resolved.

迁移现有项目时

¥When migrating an existing project

信息

在以前由 Yarn Classic 安装的项目中运行 yarn install 将导致 Yarn PnP 自动禁用,以使迁移更顺畅。你仍将受益于现代版本中实现的增强的稳定性和其他功能,并且可以决定是否在以后花时间迁移到 PnP。

¥Running yarn install in a project which used to be installed by Yarn Classic will cause Yarn PnP to be automatically disabled, to make the migration smoother. You'll still benefit from the enhanced stability and other features implemented in modern releases, and can decide whether to spend the time to migrate to PnP or not at a later time.

现有项目可能更难迁移到 Yarn PnP,原因如下:

¥Existing projects can be tougher to migrate to Yarn PnP for a couple of reasons:

  • 你已经从大量依赖开始,因此可能会列出幽灵依赖的包数量会成比例增加

    ¥You already start with a lot of dependencies, so there'll be a proportionally higher amount of packages that may list ghost dependencies

  • 它们可能被锁定在各自包的旧版本上,因此更有可能包含幽灵依赖

    ¥They may be locked on old versions of their respective packages, and thus have a higher chance to contain ghost dependencies

  • 你自己的脚本可能会无意中依赖某些实现细节或幽灵依赖,有时甚至在你没有意识到的情况下。

    ¥Your own scripts may inadvertently rely on some implementation details or ghost dependencies, sometimes even without you realizing it.

这些都不是阻止程序,但它们意味着将现有项目迁移到 Yarn PnP 可能需要几天时间。但是,我们提供了一些工具来简化其中的一些过程,查看下面的提示将帮助你更快地识别导致某些事情发生的原因,因此这并非不可能。

¥None of these are blockers, but they mean it can take a couple of days to migrate an existing project to Yarn PnP. We however provide tools to simplify some of this process, and taking a look at the footguns below will help you identify quicker what way cause something to break, so it's not impossible.

请记住,迁移到 Yarn PnP 是可选的:你可以随时通过在项目的 .yarnrc.yml 文件中设置 nodeLinker: node-modules 设置来恢复到 node_modules 安装。

¥Remember that migrating to Yarn PnP is optional: you can revert to node_modules installs at any time by setting the nodeLinker: node-modules setting in your project's .yarnrc.yml file.

Footguns

对等依赖

¥Peer dependencies

对等依赖功能强大,但很难实现 - 对于非 PnP 项目更是如此,它们必须在文件系统层次结构允许的限制范围内工作。

¥Peer dependencies are powerful, but are very difficult to implement - even more so for non-PnP projects, which have to work within the limits of what the filesystem hierarchy allows.

另一方面,Yarn PnP 没有这个限制,并且会准确地表示依赖树中每个项目的对等依赖 - 甚至工作区。如果工作区具有对等依赖,并且如果此依赖由取决于其祖父的不同版本实现,则工作区将实例化两次,每个唯一的 "依赖集" 实例化一次。

¥Yarn PnP, on the other hand, doesn't have this limitation, and will accurately represent the peer dependencies of every project in your dependency tree - even workspaces. If a workspace has a peer dependency, and if this dependency is fulfilled by different versions depending on its grandparent, then the workspace will instantiated twice, once for each unique "dependency set".

这是正确的行为,但如果你的项目大量使用对等依赖而不确保它们始终由完全相同的版本实现,则可能会导致实例化工作区数量意外激增。

¥This is the correct behaviour, but it may cause an accidental explosion of the number of instantiated workspaces if your project heavily uses peer dependencies without ensuring they are always fulfilled by the exact same versions.

共享二进制文件

¥Shared binaries

Yarn 可以防止项目所依赖的包中出现幽灵依赖,也可以防止你自己的代码中出现幽灵依赖 - 这是为了降低软件包在你的开发机器上运行但在发布后中断的可能性。

¥Yarn prevents ghost dependencies in the packages your project depends on, but also in your own code itself - this is to decrease the chances that a package would work on your development machine but break once published.

但是,当涉及到 bin 时,它会产生副作用。如果你在项目的根目录中列出了 typescript,则 tsc 二进制文件将在根包中可用,但仅在根项目中可用。换句话说,任何在其脚本中使用 tsc 二进制文件的工作区都需要在其依赖中声明它。

¥It however has a side effect when it comes to bins. If you have typescript listed at the root of your project, the tsc binary will be available in the root package but only in the root project. In other words, any workspace using the tsc binary in its scripts will need to declare it in its dependencies.

避免此类问题的一个好建议是让 "tooling" 工作区包含你的基础架构工具和脚本,并让所有其他工作区都依赖于它。

¥A good recommendation to avoid this kind of issue is to have a "tooling" workspace to contain your infrastructure tools and scripts, and have all other workspaces depend on it.

常见问题

¥Frequently asked questions

与 npm / pnpm 兼容

¥Compatibility with npm / pnpm

Yarn PnP 的设计目标是使用与其他包管理器完全相同的 "公共接口",不同之处在于已经存在的实现细节。如果项目使用 Yarn PnP,它应该可以在任何地方工作!

¥Yarn PnP was designed to use the exact same "public interfaces" as other package managers, with differences being kept to what already were implementation details. If a project works with Yarn PnP, it should work everywhere!

但有一个警告:相反的情况并不总是正确的。由于其他包管理器没有/无法强制执行正确的依赖列表,因此它们更容易意外地将幽灵依赖发送给其消费者。这样,使用 Yarn PnP 可以看作是生态系统健康的良好做法!🙂

¥One caveat though: the opposite isn't always true. Since other package managers don't / can't enforce proper listing of dependencies, they are more vulnerable to shipping ghost dependencies by accident to their consumers. In that way, using Yarn PnP can be seen as a good practice for the health of the ecosystem! 🙂

如何修复 ghost 依赖?

¥How can I fix ghost dependencies?

可以使用 packageExtensions 设置解决幽灵依赖,这允许你向依赖树中的任何包添加新的依赖。例如,如果你遇到诸如 @babel/core tried to access @babel/types, but it isn't declared in its dependencies 之类的错误,你可以通过将以下内容添加到 .yarnrc.yml 文件来轻松修复它:

¥Ghost dependencies can be solved using the packageExtensions setting, which allows you to add new dependencies to any package in your dependency tree. For example, should you face an error such as @babel/core tried to access @babel/types, but it isn't declared in its dependencies, you can easily fix it by adding the following to your .yarnrc.yml file:

packageExtensions:
"@babel/core@*":
dependencies:
"@babel/types": "*"

有时扩展 peerDependencies 字段而不是 dependencies 字段可能更有意义,这需要逐案解决。

¥It may sometimes make sense to extend the peerDependencies field rather the dependencies field, this is to be addressed case-by-case.

提示

为了避免你添加太多 packageExtensions 条目,Yarn 团队维护了一个我们会自动修复的 生态系统中已知的幽灵依赖 列表。此列表由 Yarn 和 pnpm 使用,我们非常乐意在那里合并贡献。

¥To avoid you having to add too many packageExtensions entries, the Yarn team maintains a list of known ghost dependencies in the ecosystem that we automatically fix. This list is used by both Yarn and pnpm, and we're more than happy to merge contributions there.