Element Plus 组件库相关技术揭秘:8. 为什么组件库或插件需要定义 peerDependencies


theme: v-green

前言

peerDependencies 表示同版本依赖,简单来说就是,如果你安装我,那么你最好也安装我对应的依赖。比如说我们目前正在学习的 Element Plus 组件库,它需要宿主环境提供指定的 Vue 版本来搭配使用,所以 Element Plus 组件库项目需要在 package.json 文件中进行如下配置:

"peerDependencies": {
   "vue": "^3.2.0"
}

我们知道 Element Plus 是 Vue3 的组件库,所以必然是需要运行在 Vue3 的环境下。

那么为什么 npm 包规范需要在 package.json 定义一个 peerDependencies 属性呢?使用了 peerDependencies 之后又会造成什么问题呢?不使用 peerDependencies 可不可以呢?本文将围绕这些疑问进行发布一个 npm 包来展开探索。

依赖版本号的前缀代表的意思

我们今天是探讨 peerDependencies 属性,所以我们创建一个名为 peer-dependencies-cobyte 目录,然后进到目录之后初始化 npm 项目,也就是在命令终端运行以下命令:

npm init -y

运行命令之后会在项目根目录下生成一个 package.json 的文件。我们假定我们这个 npm 包依赖 vue@3.2.0,所以我们需要进行指定版本安装:npm install vue@3.2.0。安装之后我们就可以在 package.json 下出现了以下内容:

"dependencies": {
"vue": "^3.2.0"
}

又因为安装的时候会默认使用前缀 ^,我们是指定版本,我们不希望添加前缀 ^,所以我们在安装之前需要进行去除默认使用前缀的操作。

我们删掉上述安装的 vue@3.2.0 的包:npm uninstall vue,之后我们再进行以下操作。

不使用前缀,保存确切版本 :npm config set save-exact true

执行上述操作之后,我们再进行安装:npm install vue@3.2.0。安装之后我们发现 package.json 中关于 vue 版本的定义选项中就没有了前缀 ^ 符号了。

"dependencies": {
"vue": "3.2.0"
}

等我们项目试验完毕之后,我们还是需要还原原来的默认设置,我们只需要进行以下操作即可。

设置 npm 安装包版本的使用前缀:npm config set save-prefix '^',此外还可以设置 ~ 符号。有可能会设置无效,那么这个时候我们还可以直接手动设置,也就是找到系统根目录的 npm 配置文件 .npmrc ,进行手动配置。以 window 为例,这个文件在以下目录:C:\Users{账户}\.npmrc ,进行以下设置:

save-prefix=^

上面的确切版本也可以手动设置:

save-exact=true

package.json 中依赖版本号的前缀代表的意思:

  • ~ 波浪号,匹配最新补丁版本号,即版本号的第三个数字,例如 ~5.0.0 就会匹配 5.0.x 版本,将在 5.1.0 停止
  • ^ 插入符号,匹配次要的版本号,即版本号的第二个数字,例如 ^5.0.0 就会匹配任何 5.x.x版本,将在6.0.0 停止
  • >、<、>=、<= 比较运算符,匹配的就是这个区间的版本,例如 >2.0.0 <= 2.1.4 ,就会匹配这个区间的版本号

发布一个 npm 包

发布一个 npm 包其实很简单,我们先要在 npm 官网:www.npmjs.com 注册一个账号。然后打开终端切换到对应的 npm 包所在的目录,然后进行以下操作:

  1. 在终端登录 npm

在终端输入:

npm login

然后依次输入用户名、密码、邮箱、one-time 密码

Username:
Password:
Email: (this IS public)
Enter one-time password from your authenticator app:

注意:输入密码的时候,不会显示输入的字符,one-time 密码 是 npm 发送到你注册时填写的邮箱中的一个验证码

登录成功后会显示以下内容:

Logged in as 用户名 on https://registry.npmjs.org/.

  1. 发布 npm 包

接着在终端运行  npm publish 命令,就可以将包发布到 npm 上了。

npm publish

注意:包名不能重复,发布之前最好去 npm 官网查询一下,看看对应的包名被占用了没有。

发布成功之后在终端会出现以下内容:

+ peer-dependencies-cobyte@1.0.1

  1. 删除已发布的包

最后删除已发布的包,发布的时候需要慎重一点,尽量不要往 npm 上发布没有意义的包。比如我们这次发布的包只是为了测试,所以测试完毕我们就要把它删除掉。

删除已发布的包,只需要运行 npm unpublish 包名 –force 命令,即可从 npm 删除已发布的包。另外需要注意的是只能删除72小时以内的包,删除之后24小时以内不允许重复发布。

npm unpublish peer-dependencies-cobyte --force

不使用 peerDependencies

我们再进行创建一个测试项目进行测试。创建 test-peer 目录,然后进到 test-peer 根目录下运行 npm init -y 初始化 npm 项目。

然后我们安装 Vue:npm install vue,这样会默认安装最新版本的 Vue。我们看到 package.json 的 dependencies 选项会出现 vue 相关的内容:

"dependencies": {
    "vue": "^3.2.45"
}

我们可以看到 3.2.45 是目前 Vue 最新的版本。此外我们也可以在 node_modules 目录下看到了 Vue 的相关文件。

vue.3.2.45.png

接着我们安装我们上面发布到 npm 的包 npm install peer-dependencies-cobyte,安装完之后我们发现node_modules 目录下的 peer-dependencies-cobyte 目录中还存在 node_modules 目录,目录中的内便是 Vue 相关的包目录。

peer-dependencies-cobyte.png

这个时候我们需要复习一下 npm install 的时候发生了什么,其实我们在前面章节已经详细讨论过这个问题了。具体可以查看我掘金上的这篇文章《从终端命令解析器说起谈谈 npm 包管理工具的运行原理》。这里我们再简单复述一下。

执行 npm install 的时候会检查项目中有没有 package-lock.json 文件,有则检查 package-lock.json 文件和 package.json 文件的依赖声明版本是否一致。

如果没有 package-lock.json 文件,则会根据 package.json 文件递归构建依赖树,然后按照构建好的依赖树下载完整的依赖资源。

关键在构建依赖树的时候,会遵循扁平化的原则,无论是直接依赖还是子依赖的依赖,都优先将其放在 node_modules 根目录下。在这个过程中遇到相同的依赖时,会先判断已经放置在依赖数中的依赖版本是否符合新模块对依赖版本的要求,如果符合就将其丢弃,不符合则在新模块的 node_modules 目录下放置该依赖。

很明显我们新项目中安装 Vue 版本是 3.2.45,而我们在上面设置 peer-dependencies-cobyte 包对 Vue 的依赖版本恒定为 3.2.0,所以 npm install 安装构建依赖树的时候,首先对项目中的声明进行依赖构建,那么项目中的 Vue 依赖声明的版本是 3.2.45,然后再进行递归循环构建子依赖的依赖的时候,在依赖包 peer-dependencies-cobyte 中发现相同的 Vue 依赖,然后进行版本判断的时候,发现已经存在依赖树中的 Vue 版本依赖不符合 peer-dependencies-cobyte 模块对 Vue 依赖版本的要求,所以就需要在 peer-dependencies-cobyte 模块下的 node_modules 目录中构建新的 Vue 依赖。

如果我们不希望出现这种重复安装依赖的情况,我们可以根据上文中说到的 package.json 中依赖版本号的前缀代表的意思,进行设置。比如我们就可以在 peer-dependencies-cobyte 包中设置对 Vue 的版本依赖不是恒定的,比如设置 ^ 开头。^ 插入符号,匹配次要的版本号,即版本号的第二个数字,例如上面设置 peer-dependencies-cobyte 包对 Vue 的依赖版本更改为 ^3.2.0 就会匹配任何 3.x.x版本,将在 4.0.0 停止。所以我们 test-peer 项目中安装的 vue@3.2.45 是符合要求的,这样就不会重复安装依赖了。

更改之后的安装依赖情况截图:

peer-dependencies-cobyte-vue3.2.0.png

可以看到我们把 peer-dependencies-cobyte 包对 Vue 的依赖版本更改为 ^3.2.0 之后再次发布新版本的 npm 包,再在进行安装的时候,根目录的 node_modules 目录下的 peer-dependencies-cobyte 依赖包已经没有了 Vue 相关的依赖了。

但很明显这种利用依赖前缀的方式解决宿主环境依赖与插件依赖包不一致的问题,只能解决同一个大版本号下面的小版本号不同的情况,并不能解决大版本号不同的时候依赖包不一致的问题。

事实上像我们本专栏研究的 Element Plus 这类 UI 组件库又或者像一些 Webpack、Vite、Babel、Eslint 等的插件,它们都有一个共同特点,就是不能单独运行且毫无意义,它们必须运行在各自的本体环境下,比如 Element Plus 就必须运行在本体 Vue3 的环境下。它们无须对本体依赖进行声明,而应该直接使用宿主项目中的本体依赖。这个时候我们就可以使用 peerDependencies 选项对本体依赖进行声明了。

使用 peerDependencies

我们对 peer-dependencies-cobyte 包进行更改,把 Vue 依赖的声明放置到 peerDependencies 属性选项中,然后再进行更新到 npm 中。

"peerDependencies": {
   "vue": "^3.2.0"
}

这时,我们把 test-peer 项目中的 peer-dependencies-cobyte 依赖删除掉,即运行此命令 npm uninstall peer-dependencies-cobyte 。然后再重新安装上面更新之后的 peer-dependencies-cobyte 包,我们再看看根目录 node_modules 下的 peer-dependencies-cobyte 目录。

peer-dependencies-cobyte-vue3.2.0.png

此时我们发现 peer-dependencies-cobyte 目录下也没有 Vue 相关的依赖了。

我们把 test-peer 项目中的根目录 node_modules 文件删了,直接安装 peer-dependencies-cobyte

npm install peer-dependencies-cobyte

安装的依赖结果:

peer-dependencies-cobyte-alone.png

我们发现当宿主环境中没有子依赖 peerDependencies 中声明的依赖时,也会把 peerDependencies 中声明的依赖进行安装。

注意我测试安装的 npm 版本为 6.4.15.5.1,结果都如上图。所以可以得出的结论是:如果宿主环境没有依赖核心库,则会按照 peerDependencies 的声明将相关依赖安装到宿主项目的根目录的 node_modules 中。

我们把 test-peer 项目中的根目录 node_modules 文件删了,然后安装一个 Vue2 的包。

npm install vue@2.6.0

然后我们再安装 peer-dependencies-cobyte 包。

npm install peer-dependencies-cobyte

安装结果:

peer-dependencies-cobyte-vue2.6.0.png

我们发现依然是可以正常安装的,只是给了一个警告:You must install peer dependencies yourself。意思是你必须自己安装对等依赖项。

这个也可以说明我们上文说到的 peerDependencies 可以解决大版本号不同的时候依赖包不一致的问题。虽然我们上述所举的例子并不恰当,因为一个 Vue3 的插件在宿主环境依赖是 Vue2 时并不能正常运行。具体原因我们可以看一下根目录 node_modules 的安装结果:

peer-dependencies-cobyte-npm6-vue2.6.0.png

从根目录 node_modules 的安装结果我们可以看到 peer-dependencies-cobyte 包在 peerDependencies 声明的 vue@3.2.0 的依赖包根本没有被安装,自然是不能正常运行了。从这里我们也可以得出结论,当 peerDependencies 声明的依赖与宿主环境的依赖发生冲突时,主要以宿主环境中的依赖为准,而使用 dependencies 进行依赖声明的时候当子包的依赖与宿主环境的依赖发生冲突时,就会在子包的 node_modules 目录下创建子包的依赖。这样就会导致依赖重复下载,使用 peerDependency 就可以避免的核心依赖库被重复下载的问题,从而确保项目依赖中的版本是统一的。

但有些插件缺能在大版本不一致的情况下仍然能正常运行的。比如 stylelint-webpack-plugin0.10.5 版本中的 peerDependencies 选项声明内容为:

"peerDependencies": {
    "stylelint": "^8.0.0",
    "webpack": "^1.13.2 || ^2.7.0 || ^3.11.0 || ^4.4.0"
}

但我们安装一个高版本的 stylelint 依然是能正常运行的,比如安装 stylelint@14.15.0 版本时,stylelint-webpack-plugin 插件依然能正常运行。

所以当发生依赖冲突时,插件能不能运行,主要还是看插件的兼容性如何,如果插件兼容宿主的依赖,那么该插件就能正常运行。

npm 7 下的 peerDependencies

我们在上文中进行 npm 5 和 npm 6 下的 peerDependencies 测试,得出的结论是,如果宿主环境没有依赖核心库,则会按照 peerDependencies 的声明将相关依赖安装到宿主项目的根目录的 node_modules 中;同时如果发生了宿主环境依赖和 peerDependencies 的声明依赖发生冲突会有版本不兼容的警告,但仍会安装依赖。官方的资料是 npm 4 ~ 6 的版本的处理情况都一样。

那么在 npm 7 情况又如何呢?我们把 npm 的版本升级到 npm 7。如果我们使用 nvm 包管理器的话,我们只需要切换 Node 版本即可。本作者是通过 nvm 包管理器切换到 node v16.0.0 版本下,此版本下的 npm 版本为 7.10.0。

此时我们再进行以上情况进行测试。

首先是宿主环境并没有 peerDependencies 中的依赖情况下安装,仍然能够正常安装,并且把 peerDependencies 中的相关依赖都安装到根目录的 node_modules 下。

peer-dependencies-cobyte-npm7.png

能正常安装,也很好理解,因为没有依赖冲突。

如果存在依赖冲突的情况。

我们先安装 vue@2.6.0 版本的依赖,再安装 peer-dependencies-cobyte 包。

peer-dependencies-cobyte-npm7-vue2.6.0.png

我们发现在此种情况下安装的话会报错了。所以我们可以得出结论:在 npm 7 中,如果存在无法自动解决的依赖冲突,将会阻止安装并抛出错误。

从上述抛出的错误中可以看到官方给出的解决方案。

npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with –force, or –legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

在错误中建议我们使用 –force 或 –legacy-peer-deps 命令进行重新安装。那么我们就先来了解一下这两个命令是什么意思。

  • –legacy-peer-deps:安装的时候忽略所有对等依赖(peerDependencies),以 npm v4 ~ v6 的方式安装
  • –force 或 -f: 强制安装

也就是说在 npm 7 中我们遇到对等依赖冲突我们就可以使用以下方式进行安装:

npm install --legacy-peer-deps
// 或者
npm install --force

我们通过上述方式进行安装的时候,我们发现又可以成功安装了。

legacy-peer-deps.png

使用 –force 安装则会有警告

force.png

这里有一点需要注意的是,我们使用 –legacy-peer-deps 或者 –force 进行安装,只是以 npm v4 ~ v6 的方式进行安装,npm v4 ~ v6 的方式就是当有 peerDependencies 声明的依赖发生冲突时,以宿主环境的依赖为准进行安装,所以即便安装成功,也不一定代表你的项目能正常运行。

比如说我们上面测试项目中的宿主环境的 Vue 版本是 2.6.0,但 peer-dependencies-cobyte 包中的依赖要求的是 vue@3.2.0,那么是否能正常运行就看插件 peer-dependencies-cobyte 是否有进行相应的兼容性处理了,毕竟 Vue3 也是可以兼容部分 Vue2 的写法的,但像我们专栏研究的 Element Plus 则是完全不兼容 Vue2 的,所以 Element Plus 是无法运行在一个宿主环境 Vue 依赖的版本是 2.x.x 及以下的版本的。

在此我们可以做个小总结:在 npm 的之前版本(v4 – v6)中,安装的时候发生 peerDependencies 依赖冲突会有版本不兼容的警告,但仍会安装依赖并不会抛出错误,但在 npm 7 中,如果存在无法自动解决的依赖冲突,将会阻止安装,并抛出错误。这个其实主要是为了确保一个项目中的依赖版本是唯一。

pnpm 下的 peerDependencies

我们知道 pnpm 是最新的 npm 包管理工具,官方的定义是 – 速度快、节省磁盘空间的软件包管理器。 我们专栏研究的 Element Plus 组件库就是基于 pnpm 提供的 Monorepo 功能进行设计的代码项目架构。那么使用 pnpm 进行安装的时候遇到 peerDependencies 又会发生些什么呢?我们接下来就根据上面的测试,继续使用 pnpm 再进行测试一遍。

首先是宿主环境没有子依赖在 peerDependencies 中声明的依赖的情况。具体在我们测试项目中就是项目中没有安装 Vue 相关的版本的包,然后进行安装 peer-dependencies-cobyte 包。

pnpm install peer-dependencies-cobyte

安装结果:

pnpm-peer.png

我们可以看到是可以正常安装的,但会报 peerDependencies 中声明的依赖缺失。我们再看看根目录 node_modules 中的情况:

pnpm-peer_node_modules.png

从 node_modules 中安装依赖结果来看,只是安装了 peer-dependencies-cobyte 包,而 peer-dependencies-cobyte 包的 peerDependencies 中声明的依赖却没有安装。

从这里我们可以看出 pnpm 方式安装,是不会主动安装 peerDependencies 中声明的依赖的。

接着我们看看依赖有冲突的情况。

pnpm-peer-vue2.6.0.png

我们看到当宿主依赖与子依赖的 peerDependencies 依赖发生冲突时,还是能安装,但报了一个错误。

pnpm-peer-vue2.6.0_node_modules.png

从根目录 node_modules 安装的依赖来看,当发生 peerDependencies 依赖冲突时,是以宿主环境中依赖为准。其实结合上面第一个例子我们就可以知道 pnpm 是不会主动安装 peerDependencies 中的依赖的。如果要自动安装 peerDependencies 中的依赖,还需要在 .npmrc 文件中进行以下配置:

auto-install-peers=true

auto-install-peers

  • 默认值: false
  • 类型:Boolean

当值为 true 时,将自动安装任何缺少的非可选同级依赖关系。

这个时候我们再执行安装只有 peer-dependencies-cobyte 包的情况。

pnpm-peer-alone.png

这个时候我们便可以看到根目录 node_modules 下的 .pnpm 文件中多了 Vue 相关的依赖包了。也就是当在 .npmrc 文件中设置了 auto-install-peers=true 选项之后,在进行 pnpm install 的时候,便会自动安装 peerDependencies 中的依赖了。

我们在 Element Plus 的项目的根目录的 .npmrc 文件中发现了此设置选项:strict-peer-dependencies=false 那么此选项有什么作用呢?我们在 pnpm 官网找到相关的介绍:

strict-peer-dependencies

  • Default: false (was true from v7.0.0 until v7.13.5)
  • 类型:Boolean

如果启用了此选项,那么在依赖树中存在缺失或无效的 peer 依赖关系时,命令将执行失败。

我查看了一下我在测试开发中使用的 pnpm 版本是 v7.9.0,所以默认是开启的。故当依赖树中发生 peer 依赖冲突时,将报错,命令执行失败。

我们现在在 .npmrc 文件中设置此选项:strict-peer-dependencies=false ,然后再进行安装。

安装结果:

pnpm-peer-false.png

我们发现不再报错了,只是报了一个警告,跟 npm 4 ~ 6 的安装方式一样,当发生 peer 依赖冲突的时候,也只是报一个警告。

到这里我们可以发现 pnpm 7 和 npm 7 对 peer 依赖的处理方式是一致的,都是要求依赖要一致才能将命令执行成功。但在 npm 4 ~ 6 时代,我们养成的习惯是,peer 依赖是我们自己解决,而且作为一个成熟的开发者应该是知道相关插件的 peer 依赖与宿主环境依赖的关系的,就比如说我们大专栏研究的 Element Plus 的 peer 依赖是 Vue3 一样,我们是十分清楚的,所以我们不会在 Vue2 的环境下去使用 Element Plus。因此我们会在 .npmrc 文件中设置此选项:strict-peer-dependencies=false

不同 xxxDependencies 依赖类型声明的区别

我们知道在 package.json 文件中常见的依赖声明类型有 dependencies(生产依赖)、devDependencies(开发依赖) 和 本文中讨论的 peerDependencies(同版本依赖),主要是这三种,此外还有 bundledDependencies(捆绑依赖)、optionalDependencies(可选依赖)。

dependencies 表示生产依赖,在此选项中定义的依赖都是需要在生产环境上运行的。当它作为一个 npm 包中的依赖声明被引用下载时,在此选项中定义的依赖都会被下载,在上文中我们在谈到 npm install 机制的时候,也说到了这一点,其中还有一个点就是如果在依赖树中已经存在相关依赖了,那么就需要进行依赖版本的判断,如果符合那么就会被丢弃,不符合那么就会在该模块下的 node_modules 中新建一个依赖项。

devDependencies 表示开发依赖,在此选项中定义的依赖都是不需要在生产环境上运行的,一般只在开发阶段起作用或者只在开发环境中被用到。当它作为一个 npm 包中的依赖声明被引用下载时,在此选项中定义的依赖都不会被下载。

peerDependencies 表示同版本依赖,简单来说就是,如果你安装我,那么你最好也安装我对应的依赖。就比如你安装 Element Plus,那么你也需要安装 Element Plus 的 peer 依赖,因为 Element Plus 脱离了 peer 依赖是无法单独运行的,它必须宿主环境提供对应的 peer 依赖。

我们在讲 dependencies 时说到,执行 npm install 时当子依赖的依赖版本在已经构建的依赖树中匹配不到时,就会重复下载。而 peerDependencies 则可以解决这个问题,在 peerDependencies 中定义的依赖,不会重复下载,但会要求宿主环境提供 peer 依赖一致的版本,否则在安装的时候提示警报亦或报错中断命令的执行。

这里我们还需要说明的是,执行 npm install 的时候,会同时安装 dependencies 和 devDependencies 中的依赖到 node_modules 中。所以在一个宿主项目中,其实把依赖在 dependencies 或者 devDependencies 中进行声明,本质上没区别。但如果我们的项目是一个 npm 包的项目,将来会被当作 npm 包进行引用下载的时候,就需要注意了。我们就需要把那些只在开发阶段起作用或只在开发阶段使用的依赖,比如 webpack、eslint、babel 等放在 devDependencies 中进行声明了。因为将来安装 npm 包的时候,devDependencies 中声明的依赖是不会被下载的。

最后说一下打包的问题,不管是 dependencies 还是 devDependencies 还是 peerDependencies 中的依赖,只要在项目中进行了引用,那么打包的时候都会把在项目中引用了的依赖进行打包,也就是说并不是放在 devDependencies 中依赖就不会被打包。即便在 dependencies 进行了声明,也可以在打包工具中进行设置不进行打包到 bundle 文件中。一般来说在 peerDependencies 中的声明的依赖都在打包工具中设置不打包到 bundle 文件中,从而达到减少打包文件的体积。

所以对于一般普通项目来说,dependencies 和 devDependencies 选项更多的是起到规范的作用。

这里我们只作常用的三个依赖声明类型的讲解,其他选项就不作过多解读了。

总结

为了深入理解 peerDependencies 我们发布了一个 npm 包进行了各种环境下的测试,从而加深了 npm install 机制的理解。

如果在一个 npm 包的 dependencies 中定义依赖,执行 npm install 时当子依赖的依赖版本在已经构建的依赖树中匹配不到时,就会重复下载。而 peerDependencies 则可以解决这个问题,在 peerDependencies 中定义的依赖,不会重复下载,但会要求宿主环境提供 peer 依赖一致的版本,否则在安装的时候提示警报亦或报错中断命令的执行。

具体就是不管是 npm 还是 pnpm 都会以宿主环境中依赖版本为主,区别就是不同版本的处理方式不一致,npm v4 ~ v6,发生 peer 依赖不一致的时候,会继续安装完成,但会提示一个警报。npm 7 则会报错并中断命令执行。pnpm 新版本处理方式跟 npm 7 一致(目前官网的资料显示是 v7.0.0 ~ v7.13.5)。

在不同的环境下执行安装命令,peerDependencies 选项都会有不一样的表现,但总的来说无论是 npm 还是 pnpm 都是为了更好地遵循 peerDependencies 的核心要义,就是要求宿主环境提供 peer 依赖一致的版本

只有深入理解了这些,才会深刻理解组件库或插件中为什么要在 package.json 文件中定义 peerDependencies 选项。

欢迎关注本专栏,了解更多 Element Plus 相关知识。

本专栏文章:

1. Vue3 组件库的设计和实现原理

2. 组件库工程化实战之 Monorepo 架构搭建

3. ESLint 核心原理剖析

4. ESLint 技术原理与实战及代码规范自动化详解

5. 从终端命令解析器说起谈谈 npm 包管理工具的运行原理

6. CSS 架构模式之 BEM 在组件库中的实践

7. 组件实现的基本流程及 Icon 组件的实现

8. 为什么组件库或插件需要定义 peerDependencies

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容