辛宝Otto

Be a Happy Man

最近在学 Nuxt.js

【汇总贴】深入浅出管理 uni-app 依赖的 uvm

发布于2023-11-23

更新于2023-11-23

归类blogs

Tagpost

汇总贴:深入浅出管理 uni-app 依赖的 uvm

【汇总贴】深入浅出管理 uni-app 依赖的 uvm 汇总贴:深入浅出管理 uni-app 依赖的 uvm

经验分享的目标

逛论坛发现有一些问题归属于@dcloudio/uvm ,问题相似,解答也相似,这里做个汇总帖子,争取能一次性解决常见问题。

本文目标是带读 uvm 源码,熟悉和理解 uvm 设计思路和作用,并汇总常见问题,最终达到熟悉 uvm 源码能自主解决问题的目的。

uvm 全称是 uni-app version manager,可以类比 nvm - node version manager

背景介绍

uvm 作为官方的解决方案,在官网文档 https://uniapp.dcloud.net.cn/quickstart-cli.html#cliversion 有明显提及:

可以使用  @dcloudio/uvm  管理编译器的版本,此工具仅自动增加或更新 uni-app 编译器主要依赖,对于新增的编译命令(scripts)暂时不会自动处理,需手动参考新工程进行配置。

内部跳转 [[uni-app 学习#uni-app 依赖升级 uvm]]

作为一个高速迭代的技术框架,uni-app 和周边生态每天都会开发编码、修复问题。出了常规的前端方案,还需要同时支持 hbuilderX,陈旧版本的依赖是没有意义的,是有最新版本对当前项目有明显的意义。

通过一种方案,实现前端依赖自动升级就显得有必要了,这个过程最好是一键升级,不需要关注当前的版本,和最新的版本具体是什么。

因此 @dcloudio/uvm 就出现了,在项目目录中执行 npx @dcloudio/uvm 就能实现 package.json 相关依赖的自动更新。

出于一些历史原因,uvm 的项目源码没有放到公网展示,但 npm 的产物没有走压缩编译,阅读产物源码和开发源码效果一致,这里简单介绍下源码和大致代码结构。

项目源码

访问 npm 仓库 @dcloudio/uvm,点击 Code 部分。可以看到最终的产物,和原始代码没什么区别。https://www.npmjs.com/package/@dcloudio/uvm?activeTab=code

Pasted image 20231123175924

首先看 package.json 识别相关依赖和注册命令。

package.json

"dependencies": {
"cross-spawn": "^7.0.3",
"inquirer": "^8.2.0",
"minimist": "^1.2.5",
"node-fetch": "^2"
},
"devDependencies": {
"xmldom": "^0.6.0"
}

依赖比较简单,跨平台执行命令,交互式 cli,请求发送。相关的,主要还是看 bin

命令注册看 bin 注册 bin/index.js,源码如下,相对比较简单和整齐。

Pasted image 20231122171157

通过阅读源码可以看到,当通过注册命令启动时候,会读取当前 argv 参数,比如不传的话默认填写 latest,也会读取当前命令行所代表的项目地址。

流程

在截图 line30 会执行 start(),会依次经历 info() find()get(),最后是 merge()

info()

info() 方法中,会读取当前目录的 package.json 去分析 devDependencies 依赖是否包含下面的依赖:

const plugins = {
  vue2: '@dcloudio/vue-cli-plugin-uni',
  vue3: '@dcloudio/vite-plugin-uni'
}

最终返回对应的依赖和是否 vue3,这意味着 vue2 和 vue3 要升级的依赖是不同的。这里挖个坑,后面带读 @dcloudio/vite-plugin-uni 依赖的原理,感兴趣请评论区告诉我。

find()

定位到 /@dcloudio/uvm/lib/version.js#find

find 方法中,会读取当前的 plugin 、和 具体版本特征值或者"latest" 、是否 vue3 进行传递判断。

实际处理使用拿到当前的版本最后一段数字,去远程拉数据,不外乎 latest alpha release 三个取值。获取远程的 version 值。比如 3.96.2023110403,这里截图打个断点看看:

Pasted image 20231122173836

断点进相关历史判断。

之后继续请求 'https://registry.npmmirror.com/@dcloudio/vite-plugin-uni',还是各种特征值判断。

这里会请求远程数据,获取 hbuilderX 版本信息,和 registry 信息。这里是对网络有要求。

Pasted image 20231123181106

这里的 registry 默认是 cnpm ,这依赖 cnpm 的稳定性,有一定网络波动的风险,但起码能保证国内用户能访问通。

get()

还会继续判断 get 方法,这里以 vue3 的为例,最终回去获取 https://gitee.com/dcloud/uni-preset-vue/blob/vite/package.json 的信息,也就是以远程库为基准进行处理。

为了兼容历史版本的变更,这里逻辑基本不可读了,做了大量版本的兼容。在翻阅 ask 社区 uvm 问题时候,可以看到一些兼容的血泪史。

这里如果是 vue3 会进入 getVue3(),这里默认会读取 gitee 的远程仓库信息。如果出于某种原因,github 和 gitee 仓库没有正确同步,就会导致信息不匹配。

merge()

这一步骤是最后的执行阶段。会拿到 deps ,准备具体执行。使用 spawnAsync 去分别更新依赖和开发者依赖。

这一块使用 cross-spawn 绕过不同系统执行命令的问题。

技术总结

通过上面简单的核心逻辑带读,可以看到 uvm 执行的本质思路是跨系统执行命令,获取当前版本信息和远程 gitee 上典型仓库的 package.json,进行 复杂 判断,得出要升级的结果。

常见 QA

运行报错提示 Not find version xxx ,请提供具体版本号

这个目前已经没有类似问题了,对一些历史陈旧的版本可能存在一定问题,如果你遇到了请提供你当前的版本号。

运行报错提示 code ETARGET

可能是缓存和网络问题,尝试删除缓存和当前 node_modules ,尝试重新安装依赖。 也可能是网络同步问题,发版之后同步到国内需要一定时间,可以休息一下等一等再尝试。