小程序更新机制分析
按照微信小程序官方文档的说明,小程序的更新机制主要分为未启动时更新和启动时更新两种模式。启动时更新会在小程序冷启动时异步检查是否有新版本,如果有新版本,会下载下来,等下次冷启动时候使用新版本代码进行启动;而未启动时更新会有定时检查器对最近7天内使用过的小程序进行定时检查是否有新版本,每6小时一次,如果有新版本就会预下载,下次冷启动时候可以直接使用最新的版本。
小程序从基础库1.9.90开始就提供了wx.getUpdateManager接口,使用该接口,可以获知是否有新版本小程序、新版本是否下载好以及应用新版本的能力。在官方推荐的最佳实践里,为了保证用户体验,建议只在非常必要时才弹框提醒用户强制更新。
而我们需要更新的时机是不确定的,有时候是紧急bug修复需要强制更新,有时候是活动新功能希望快点覆盖全部用户也需要强制更新,所以我们需要一套可以灵活控制用户版本更新的下发机制。
强制更新版本配置下发
要达到可以进行版本比对,并判断是否强制更新的目的,首先必须保证小程序发布有标准的版本管理并将版本号注入代码中,然后通过接口下发需要更新的最小版本号跟当前打开的版本进行比对来完成强制更新的提醒。
版本管理和版本注入
我们的小程序项目中通过standard-version进行版本号管理,遵循Semver语义化版本规范。每次发布时通过standard-version进行打tag并更新package.json文件中的version字段
tag的push会触发流水线构建,在npm run build阶段,通过提取获取package.json中的字段,并通过gulp-batch-replace插件进行版本号替代,完成版本号在代码中的注入
代码语言:javascript复制// gulpfile.js
const gulpif = require('gulp-if');
const batchReplace = require('gulp-batch-replace');
const version = require('./package.json').version;
gulp.src('src/**/*.js').pipe(gulpif(
isEnvJs,
batchReplace([
['process.env.APP_VERSION', JSON.stringify(version)],
...
]),
))
代码语言:javascript复制// env.js
const env = {
version: process.env.APP_VERSION,
...
}
版本配置管理
在后端版本配置上,我们采取了更细粒度的版本控制配置,分别细分为应用级别的版本控制,页面级别的版本控制和接口级别的版本控制。
- 应用级别(app)
应用级别的配置是用户进入到小程序,在app.onLaunch阶段进行版本号判断
- 页面级别(pages)
在用户访问页面时,如果页面路径命中配置路径,会触发版本号判断
- 接口级别(apis)
在用户使用小程序请求到某个指定接口时,会触发版本号判断
通过将版本控制的粒度细化,如果用户访问时有新的版本下载,但用户当前访问的页面或者接口没有需要强制更新的诉求,那就不需要弹框提醒用户进行强制更新,更深度的保证用户体验。
客户端版本比对
在小程序侧,通过Object.defineProperty()方法对wx.request方法进行代理以及对全局的Page.onLoad方法进行重写注入检查更新代码。
如通过wx.getUpdateManager()检查到当前有新版本下载完成,且当前request请求的url命中上述配置中apis中的接口路径或者page.onLoad中的this.route命中pages中配置的路径,即可弹窗提醒用户强制更新。
代码语言:javascript复制function getVersionArr(versionStr) {
const str = versionStr;
return str.match(/[0-9] /ig);
}
// 版本比对方法
export function shouldUpgrade(currentVersion, targetVersion = '0.0.0') {
const currentVersionArr = getVersionArr(currentVersion);
const targetVersionArr = getVersionArr(targetVersion);
const len = Math.max(currentVersionArr.length, targetVersionArr.length);
while (currentVersionArr.length < len) {
currentVersionArr.push('0');
}
while (targetVersionArr.length < len) {
targetVersionArr.push('0');
}
for (let i = 0; i < len; i ) {
const current = parseInt(currentVersionArr[i], 10);
const target = parseInt(targetVersionArr[i], 10);
if (current !== target) {
return current < target;
}
}
return false;
}
自动化强制版本更新
问题分析
上面提到是通过手动更改配置下发的方式来实现强制版本更新,但微信侧小程序的版本管理里还有一个隐含特性,就是小程序代码包cdn对代码包版本的缓存是有时间和数量限制的,目前限制是30天内发布小程序版本不能超过30个。
例如30天内发布了v1 ~ v31这31个版本,一个用户之前访问v1的版本的小程序,然后很久都没再来访问,那么当你发布了v31版本时用户来访问了,小程序冷启动阶段默认使用的是用户本地缓存的v1版本的代码包,当用户操作跳转到v1版本的其他分包页面时,由于微信服务端v1版本的代码包缓存已经过期,会导致出现“网络无法连接”的错误。
这种情况下我们需要一个自动获取小程序前30个版本号的功能,并将版本号更新到下发的配置中,作为小程序app启动阶段一个强制更新的判断依据。
但前面提到我们的版本号是基于semver语义化的版本号,格式如X.Y.Z的模式,用于做版本大小判断尚可,但是要通过两个版本号来计算出是否相差了30个版本,这个就无法计算了。
通过git的tag个数提取版本号
无法直接通过semver的版本号计算出前30个版本的版本号的值,但得益于我们每次发布都是通过打tag进行发布的,所以可以通过git中所提交的tag数,每次发布时候往回找20个版本(这里用20个是为了防止有时候发布后回滚再发布,同一个版本号发布了多次)的版本号。
代码语言:shell复制git ls-remote --tags --refs --sort="v:refname" | tail -n20 | sed 's/.*///'
每次版本发布时,将前第20个tag对应的版本号更新到后台的配置中,这样就达到了自动更新最低版本号的目的,有效的避免了因为版本发布过于频繁引起的用户访问错误。