跳至主要内容

架构

通用架构

Yarn 通过一个核心包(发布为 @yarnpkg/core)运行,该包公开构成项目的各种基础组件。某些组件是你可能从 API 中识别的类:ConfigurationProjectWorkspaceCacheManifest 等。所有这些都由核心包提供。

核心本身不会做太多事情 - 它仅仅包含管理项目所需的逻辑。为了从命令行使用此逻辑,Yarn 提供了一个称为 @yarnpkg/cli 的间接调用,有趣的是,它也不会做太多事情。但是它有两个非常重要的职责:它根据当前目录 (cwd) 注入项目实例,并将预构建的 Yarn 插件注入环境。

Yarn 以模块化方式构建,允许将与第三方交互相关的大部分业务逻辑外部化到它们自己的包中 - 例如 npm 解析器 只是众多插件之一。此设计为我们提供了更简单的代码库(因此提高了开发速度和产品的稳定性),并为插件作者提供了编写自己的外部逻辑的能力,而无需修改 Yarn 代码库本身。

安装架构

运行 yarn install 时发生的情况可以概括为几个不同的步骤

  1. 首先,我们进入“解析步骤”

    • 首先,我们加载存储在锁定文件中的条目,然后根据这些数据和项目的当前状态(通过读取清单文件,又名 package.json 来确定),核心运行一个内部算法来找出缺少哪些条目。

    • 对于每个缺少的条目,它使用 Resolver 接口查询插件,并询问它们是否知道一个与给定描述符相匹配的包(supportsDescriptor)及其确切标识(getCandidates)和传递依赖项列表(resolve)。

    • 一旦获得新的包元数据列表,核心就会在新添加的包的传递依赖项上启动新的解析过程。这将重复进行,直到它确定依赖项树中的所有包现在都将它们的元数据存储在锁定文件中。

    • 最后,一旦依赖项树中的每个包范围都被解析为元数据,核心就会最后一次在内存中构建树,以便生成我们称之为“虚拟包”的内容。简而言之,这些虚拟包是同一基本包的拆分实例 - 我们使用它们来消除列出对等依赖项的所有包的歧义,这些包的依赖项集会根据它们在依赖项树中的位置而改变(有关更多信息,请查阅 此词典条目)。

  2. 解析完成后,我们进入“获取步骤”

    • 现在我们有了构成依赖项树的确切包集,我们对其进行迭代,并为每个包向缓存发起一个新请求,以了解是否可以在任何地方找到该包。如果不是,我们就像在上一步骤中所做的那样,询问我们的插件(通过 Fetcher 接口)它们是否知道该包(supports),如果是,则从其远程位置检索它(fetch)。

    • 关于获取器的有趣小知识:它们通过 fs 上的抽象层与核心进行通信。我们这样做是为了让我们的软件包可以来自许多不同的源 - 它可以来自注册表中下载的软件包的 zip 存档,也可以来自 portal: 依赖项的磁盘上的实际目录。

  3. 最后,一旦所有软件包都准备好使用,就到了“链接步骤”

    • 为了正常工作,您使用的软件包必须以某种方式安装在磁盘上。例如,在原生 Node 应用程序的情况下,您的软件包必须安装到一组 node_modules 目录中,以便解释器可以找到它们。这就是链接器的作用。通过 LinkerInstaller 接口,Yarn 核心将与注册插件通信,让他们了解依赖项树中列出的软件包,并描述它们之间的关系(例如,它会告诉他们 tapablewebpack 的依赖项)。然后,插件可以决定如何以他们认为合适的方式处理此信息。

    • 这样做意味着可以非常轻松地为其他编程语言创建新的链接器 - 您只需要编写自己的逻辑,说明 Yarn 提供的软件包应该发生什么。想要生成 __autoload.php 吗?去做吧!想要设置 Python 虚拟环境吗?没问题!

    • 另一个很酷的事情是依赖项树中的软件包不必都是同类型的。我们的插件设计允许同时实例化多个链接器。更好的是 - 软件包可以在链接器之间相互依赖!您可以让 JavaScript 软件包依赖 Python 软件包(例如,从技术上讲,node-gyp 就是这种情况)。