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

相关推荐
前端熊猫20 分钟前
css实现垂直文本
前端·css
JINGWHALE130 分钟前
设计模式 结构型 桥接模式(Bridge Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·桥接模式
匹马夕阳35 分钟前
Vue3-跨层组件通信Provide/Inject机制详解
前端·javascript·vue.js
凌览1 小时前
高收入的程序员为什么难找到女朋友?
前端·后端
Pee Wee2 小时前
责任链模式
java·前端·责任链模式
我家媳妇儿萌哒哒2 小时前
vue Element Ui Upload 上传 点击一个按钮,选择多个文件后直接上传,使用防抖解决多次上传的问题。
前端·javascript·vue.js
pengyu2 小时前
系统化掌握Dart编程之数据类型
android·前端·flutter
前端青山2 小时前
JavaScript闭包的深度剖析与实际应用
开发语言·前端·javascript·前端框架·ecmascript
JINGWHALE12 小时前
设计模式 结构型 组合模式(Composite Pattern)与 常见技术框架应用 解析
前端·人工智能·后端·设计模式·性能优化·系统架构·组合模式
青颜的天空2 小时前
CSS 中 content换行符实现打点 loading 正在加载中的效果
前端·css