Yarn install命令源码实现

4.1 Yarn install命令源码实现

本文发布于掘金专栏
文章原文位于github
本文严重依赖2. 使用 vscode 以及 chrome 调试 yarn 源码

install 命令是 yarn 命令中比较核心的一个命令,弄懂 install 其他很多命令都会明白,因为存在互相调用函数实现。

调试准备

进入一个 debug 文件夹并初始化一个npm包,安装react@18,删除node_modules只留下package.jsonyarn.lock,命令在下面

bash 复制代码
yarn init -y
yarn add react@18    # 这里用18是因为react18有依赖项
rm -rf node_modules

vscodelaunch.json中的配置添加调试 install 相关的配置

json 复制代码
{
  "type": "node",
  "request": "launch",
  "name": "debug yarn install",
  "skipFiles": ["<node_internals>/**"],
  "console": "internalConsole",
  "outFiles": ["${workspaceFolder}/**/*.(m|c|)js", "!**/node_modules/**"],
  "program": "${workspaceFolder}/mybuild/cli/index.js",
  "cwd": "${workspaceFolder}/../yarn-source-dev",
  "args": ["install"]
}

选择debug yarn install即可调试。具体的调试方法见使用 vscode 以及 chrome 调试 yarn 源码,也就是本书的第 2 章。

install 命令运行流程

install 命令源码位于src/cli/commands/install.js,查看 install 命令源码可以得到下面的运行流程图。

运行流程主要分为四个函数的执行。

run 函数

run函数是导出给commands对象的一个函数,这是install命令具体实现的入口,下面是简洁代码,run函数主要做了以下事

js 复制代码
if (flags.lockfile === false) {
  lockfile = new Lockfile();
} else {
  lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter);
}

wrapLifecycle();
  1. 生成Lockfile实例,如果命令行参数中带有--lockfile false的话会生成一个全新的Lockfile实例,如果没有--lockfile参数的话就从当前的yarn.lock文件生成实例,这也是默认的选项。下面是从yarn.lock中生成的Lockfile实例。可以看到Lockfile是解析yarn.lock文件到对应的数据结构的类。
  1. 调用wrapLifecycle。实际这里是先调用的install函数install函数再调用的wrapLifecycle。下面是install的函数实现。

wrapLifecycle 函数

这个函数的参数是一个factory函数,factory函数会被放到一系列的lifecyclescript执行中执行。下面是 wrapLifecycle 函数的简洁声明

js 复制代码
async function wrapLifecycle(config: Config, flags: Object, factory) {
  await config.executeLifecycleScript("preinstall");

  await factory();
  await config.executeLifecycleScript("install");
  await config.executeLifecycleScript("postinstall");

  if (!config.production) {
    if (!config.disablePrepublish) {
      await config.executeLifecycleScript("prepublish");
    }
    await config.executeLifecycleScript("prepare");
  }
}

所谓的lifecyclescript实际上就是在package.json中的scripts字段中声明的script。这里执行script的函数是config.executeLifecycleScript。这个后续在yarn run命令的章节中会具体讲解。这里可以看到在执行完preinstall script 会就会立即执行factory函数。

factory 函数

factory 函数首先实例话了Install类,这个类的构造函数里面是一些属性的初始化。然后这个函数会调用Install实例上的init方法。init方法里面是后学install命令的具体实现。

init 方法

init方法的代码比较多,这里简洁分为三部分。

第一部分是执行yarn升级检测,如果yarn有版本的更新输出中会有提示,这里的yarn版本指的是yarn1的版本,20 年之前使用的人会经常看到这个提示,现在(2024)年因为yarn1的版本基本没有更新所有很难看到这个提示了。

第二部分是执行fetchRequestFromCwd函数,这个函数是获取包的入口的函数。此函数会把package.json里面依赖的包都收集到一个数组里面。这里查看代码可以看到yarn默认是安装三种类型的依赖dependencies devDependencies optionalDependencies,其中devDependencies!this.config.production === false时是不会安装的(加上--procution 命令会使 this.config.production === true)。同时这个函数还会返回当前包的一些信息比如workspace以及package.json

第三部分则是steps,在上文中的流程图中带有step的节点都是steps中的一个运行节点,运行节点是先收集需要运行的节点,然后对节点依次执行,如果当前节点执行报错则直接退出不执行后续的节点。steps的执行函数会使用init方法中的一些变量并进行修改,在整个步骤完成后init函数中的变量会被置为相应的值。

第四部分则是保存对应的package.json以及yarn.lock。第三部分结束后整个node_modules都已经构建好了。

steps

steps是init方法的核心部分。这里按照出现顺序依次介绍对应的step

在运行install命令的时候控制台会输出当前在哪一个step

checkCompatibilityStep

这个step是选择加入到steps中的,其中的执行条件是package.json中有os cpu engines字段中的其中一个且命令行参数中没有对应的ignore参数,比如oscpu对应--ignore-platform参数,engines对应--ignore-engines参数。具体检测的函数实现代码位于src/package-compatibility.js

resolveStep

这个第一个必执行的stepresolveStep的目标是找到所有包的具体信息,包括依赖的依赖的包。找到的具体信息主要是包的具体版本是什么,从哪里下载,包依赖的具体依赖包具体信息。一般情况下是在npm远端源上下载的包文件。下面时step执行函数的内容

js 复制代码
await this.resolver.init(depRequests, {
  isFlat: this.flags.flat,
  isFrozen: this.flags.frozenLockfile,
  workspaceLayout,
});
topLevelPatterns = this.preparePatterns(rawPatterns);
flattenedTopLevelPatterns = await this.flatten(topLevelPatterns);

npm远端源的包文件是一个tarball文件,它是一个以.tgz结尾的一个压缩文件。访问源的api,比如https://registry.npmjs.org/react/18.2.0,可以在响应中的dist/tarball中找到包文件的链接。

this.resolver.init会找到所有的包的准确版本以及下载包的npm源链接

这个函数内容的逻辑很复杂,概括大致能分成yarn.lock文件中存在以及在yarn.lock文件中不存在两种。如果包对应的版本在yarn.lock中存在,yarn会直接使用lock文件中的包版本以及lock文件中声明的tarball文件链接。如果包在yarn.lock中不存在,yarn会请求npm远端源去获取对应的信息,知道所有的依赖都被正确解析。

相关代码在src/package-resolver

step执行函数中剩下两步很简单,把最底层的依赖拍平。

经过这一部所有包的信息,包括依赖的依赖,也就是将要安装的所有包的信息都被解析到this.resolver中。为下一步进行下载对应的包做准备。

下面是resolveStep运行完之后解析到的react@18及其所有的包的信息。

作者因为有yarn.lock文件所有所有的包都是直接通过lock文件解析的,通过lock文件解析的包的fresh字段是false,通过源解析的是true

auditStep

这个step满足参数中有--audit才执行,因为yarn audit命令并没有修复的子命令,所有install命令这里可以加--audit来实现差不多的功能。这个在之后的yarn audit命令中会讲这里先跳过。

fetchStep

这是一个必执行的step。在上面的resolveStep把所有包的信息都解析出,这一步主要是下载包到对应的cache文件夹。逻辑也很简单,如果cache文件夹中存在对应的包,则无需操作,只需要返回包cache对应的信息,如果cache文件夹中不存在包,则下载包的tarball文件并进行解压到包的cache文件夹。

使用yarn cache dir命令可以查看全局的cache文件夹

进入cache文件夹中搜索react-18.2.0会发现存在react@18.2.0版本的cache文件夹。这是因为作者之前已经安装过react@18.2.0导致cache命中。

linkStep

这是一个必执行的step。这个step开始构建当前目录下的node_modules。这个step简单来讲就是先把第一步解析到的所有包扁平化成一个数组,找到每个包对于的cache文件夹位置以及在当前目录node_modules下的位置,然后进行文件的复制。由于resolveStepfetchStep已经知道所有的信息这里可以直接复制。

在把所有包从cache中的位置复制到node_modules之后,yarn还会把有bin的包的bin执行文件创建软链接到node_modules/.bin目录下。

pnpStep

这个step需要条件满足才执行,这里需要打开pnp插件的开关才能执行。这个不做多讲后续讲pnp会补充。

buildStep

这是一个必执行的step。这个step一般是在linkStep后被执行,同时这个step一般也是steps的最后一个。这个step主要是将之前resolveStep中收集到的所有包的scripts进行执行。这里会先按照当前包的依赖中的scripts进行执行,执行完当前的再执行依赖的,整体的执行顺序是一个拓扑序。这里执行的scriptspreinstall install postinstall,其中的声明在依赖包的scripts中的script不会被执行。

到了这里基本的step就走完,剩下的都是满足条件才执行的step

savingHarStep

这个step是一个满足条件才执行的step。这个step通过在命令行参数中加入--har来启用,启用之后在命令完成后会生成一个.har文件,这个文件可以使用网络分析工具进行分析。这个step在网络有问题排查十分有用。

cleaningModulesStep

这个step是一个满足条件才执行的step。启用这个step的条件是当前目录下存在.yarnclean文件。这个stepyarn autoclean命令有关后续会讲解。

总结

Yarn的install命令实现中最重要的是四个stepresolveStep负责找到包具体信息和来源,fetchStep把包从远端拉到本地的cache文件夹,linkStep负责构建完整的node_modulesbuildStep负责按照拓扑序执行所有依赖包的scripts

author: xiaochuan

date: 2025.1.1

相关推荐
anyup_前端梦工厂2 小时前
了解几个 HTML 标签属性,实现优化页面加载性能
前端·html
前端御书房2 小时前
前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析
前端·javascript
2301_789169542 小时前
angular中使用animation.css实现翻转展示卡片正反两面效果
前端·css·angular.js
风口上的猪20153 小时前
thingboard告警信息格式美化
java·服务器·前端
程序员黄同学3 小时前
请谈谈 Vue 中的响应式原理,如何实现?
前端·javascript·vue.js
爱编程的小庄4 小时前
web网络安全:SQL 注入攻击
前端·sql·web安全
宁波阿成5 小时前
vue3里组件的v-model:value与v-model的区别
前端·javascript·vue.js
柯腾啊5 小时前
VSCode 中使用 Snippets 设置常用代码块
开发语言·前端·javascript·ide·vscode·编辑器·代码片段
weixin_535854225 小时前
oppo,汤臣倍健,康冠科技,高途教育25届春招内推
c语言·前端·嵌入式硬件·硬件工程·求职招聘
扣丁梦想家5 小时前
设计模式教程:装饰器模式(Decorator Pattern)
java·前端·装饰器模式