Config
本文发布于掘金专栏
文章原文位于github
yarn的Config是一个很重要的类,在main函数里面和Reporter一起初始化,这两个实例也会通过参数传给后续的命令的run函数。本章会详细讲解Config有关的逻辑。
Reporter的代码没有复杂的逻辑这里直接跳过
Config的声明位于src/config.js文件,调用位于src/cli/index.js里面,除了新建一个Config实例外还调用了init方法。
Config类的属性主要由基本的配置以及ConstraintResolver RequestManager ConfigRegistries实例构成。方法主要由_init init两个方法构成。
下面是省略后的代码
class Config {
constructor(reporter: Reporter) {
this.constraintResolver = new ConstraintResolver(this, reporter);
this.requestManager = new RequestManager(reporter);
this.reporter = reporter;
this._init({});
}
// 基本的属性
enableDefaultRc: boolean;
extraneousYarnrcFiles: Array<string>;
// ...
// 主要的实例
constraintResolver: ConstraintResolver;
requestManager: RequestManager;
registries: ConfigRegistries;
}
基础的属性
基础属性分为两类,一类是通过命令行参数和常量就可以直接得到的,另一类则是需要一些计算才能得到的参数。在方法_init里面直接使用命令行参数对基础属性进行赋值。

另外一种是通过计算得到的属性,逻辑位于init方法里面。这个详见下文。
ConstraintResolver
这个实例会在构造函数被实例化出来,参数是当前的Config实例以及Reporter实例。类的源码声明于文件src/package-constraint-resolver,这个类的逻辑十分简单。下面是省略后的代码
const semver = require('semver');
export default class PackageConstraintResolver {
// ...
reporter: Reporter;
config: Config;
reduce(versions: Array<string>, range: string): Promise<?string> {
if (range === 'latest') {
return Promise.resolve(versions[versions.length - 1]);
} else {
return Promise.resolve(semver.maxSatisfying(versions, range, this.config.looseSemver));
}
}
}
其中只有一个reduce方法,这个方法是找到一个版本string对应的真正版本。比如运行命令yarn add react@latest,这里的参数range就是latest,第一个参数versions会从其他地方获取到,这里就会走到第一个if语句里面。如果使用的是一个semver的版本号,比如yarn add react@^18,就会走到else语句里面,这里就直接调用semver的maxSatisfying方法,这里的looseSemver默认是true,是Config的一个基础属性,从命令行参数得到的。

RequestManager
RequestManager也是在构造函数中被实例化的一个类,原为位于src/util/request-manager.js,这是一个网络请求的基础类,其中yarn对其做了一些封装,比如重试超时以及请求队列等等,这里不做细讲。RequestManager底层的网络请求库是使用的早已废弃的request,这里也能看出yarn很久没有进行大的迭代。
ConfigRegistries
Config中的registries分别由两个属性构成,一个npm一个yarn,两个属性分别是NpmRegistry以及YarnRegistry的实例,源码位于src/registries里面。这两个类主要是和.npmrc以及.yarnrc里面的配置以及和远端的npm源有关。
这里有一个继承,BaseRegistry -> NpmRegistry -> YarnRegistry
这里本地的Registry类统一使用英文,远端npm源统一使用npm源。
远端的npm源指的是比如yarn add react@18命令时需要去哪个链接下载,以及publish时需要发布到哪个网站。npm官方的源链接是https://registry.npmjs.org/,通常国内会使用淘宝的源https://registry.npmmirror.com/,源配置在.npmrc文件的registry字段。
NpmRegistry
NpmRegistry管理和.npmrc有关的配置及其相关远端npm源的相关逻辑。
由于继承BaseRegistry,所以NpmRegistry也有一个config属性对象表示当前的配置。其中最重要的方法是loadConfig方法,这里给出NpmRegistry的简洁代码。
class NpmRegistry extends Registry {
config: Object; // 这个config是继承来的,
async loadConfig(): Promise<void> {
// 初始化config里面的值
this.mergeEnv('npm_config_');
// 下面这个是伪代码
this.getPossibleConfigLocationsAndSet();
}
}
loadConfig中主要是两个逻辑
-
this.mergeEnv('npm_config_')从环境变量中取值并赋值到this.config。这个方法会遍历当前进程中的环境变量,环境变量名转换为小写。以
npm_config_开头的环境变量会被加入到this.config中,其中前缀npm_config_会被去掉。这里注入一个名为
npm_config_test_name的变量,值为test_value,在经过mergeEnv后this.config中会有一个名为test_name的属性值为test_value
-
寻找可能的
.npmrc文件并使用文件中的配置赋值这里会递归的寻找所有的
.npmrc文件,找到文件后取出里面的值和当前的this.config进行合并,从当前目录一直找到根目录,每层的.npmrc文件都有效,层数约接近当前的目录优先级越高,越接近根目录优先级越低。如果下层目录目录和上层目录都有同一个key,则会以底层目录的.npmrc中的值为准。很多人的home目录~里面都有一个.npmrc,这个.npmrc可以看作是全局的npm配置。这里在调试目录下新增
.npmrc文件并新增内容rctestvalue=rctestvalue。调试结果如下图。rctestvalue的值是从当前目录的.npmrc中取的,而其他的值是从上层目录的.npmrc中取得。
不止是
yarn是这个逻辑,其他的包管理器npm和pnpm都会往上寻找所有的.npmrc并进行合并。
YarnRegistry
YarnRegistry管理和.yarnrc文件有关的配置和相关npm源的内容
他继承NpmRegistry,也有一个config对象属性,其中最重要的两个方法分别是init以及loadConfig
-
init调用this.mergeEnv('yarn_'),逻辑和上面的this.mergeEnv('npm_config_')一样,只不过前缀换成了yarn_开头。如果给一个环境变量yarn_ytest=test_value,过了这步后this.config中会有一个ytest=test_value的值。
-
寻找可能的
.yarnrc文件并使用文件中的配置赋值这个逻辑和寻找
.npmrc的逻辑一致知识文件名换成了.yarnrc。这里在调试目录下新建一个
.yarnrc文件并写入yarn_test_key yarn_test_value。调试结果如下
this.config的使用
在获取了this.config的值之后就是使用了,其中使用最多的字段就是registry,这是远端npm源的链接,包管理器可以从这里下载包,发布包以及登录等一系列操作。
NpmRegistry和YarnRegistry都有一个getOption方法,这个方法返回this.config中的值。Config也有一个getOption方法,这个方法会先调用YarnRegistry的getOption如果有值直接返回,如果没有值会去调用NpmRegistry的getOption。这说明无论层级.yarnrc中的配置比.npmrc中的配置高。
这里不建议使用
.yarnrc建议使用.npmrc,.yarnrc的yarn自己的逻辑太多,而.npmrc是大部分包管理器通用的一个配置文件。
Config的init函数
上面一直没有讲的是ConfigRegistries就是在init函数中被初始化的,这个函数还负责初始化一些需要计算得到的基本属性(这些属性需要根据ConfigRegistries里面的config字段来进行初始化)。主要有以下几个类型的基础属性。
-
全局的目录
包括全局包的安装目录以及全局
link包的目录,全局cache的目录等 -
workspace相关的
如果有
workspace相关的目录以及配置也会被初始化 -
网络相关的配置
在
Config构造函数中实例化了一个RequestManager,这里会根据配置文件中的网络配置来设置这个实例的一些属性。 -
其他
其他一些配置比如
pnp插件相关
总结
到了这里Config的内容就结束了,Config是一个很重要的概念,后续命令的运行依赖他的一个实例。在main函数中被实例化后会被传到每个命令运行的参数里面。同时ConfigResolvers这个重要的类也是在Config中被使用。在配置项中使用最多的是registry,这个配置指示了包管理器从哪个地方获取包以及发布到哪里。
下一节开始正式进入具体命令的讲解
author: xiaochuan
date: 2024.12.29