汇总贴:深入浅出管理 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.png]]
首先看 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.png]]
通过阅读源码可以看到,当通过注册命令启动时候,会读取当前 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.png]]
断点进相关历史判断。
之后继续请求 'https://registry.npmmirror.com/@dcloudio/vite-plugin-uni'
,还是各种特征值判断。
这里会请求远程数据,获取 hbuilderX 版本信息,和 registry 信息。这里是对网络有要求。
![[Pasted image 20231123181106.png]]
这里的 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
,尝试重新安装依赖。 也可能是网络同步问题,发版之后同步到国内需要一定时间,可以休息一下等一等再尝试。