小程序的发版向来是个很蛋疼的过程:
- 在编辑器打好包
- 打开小程序调试工具
- 点击上传(等待...)
- 打开小程序后台管理,设置为对应环境体验版
- 等待测试验证
- 重复上述流程直到构建 prod 版本为止
- 提审、发版
在编辑器和浏览器还有调试工具之间来回切换和等待,每一个环节都充斥着浪费的时间,如果能减少其中某些环节的消耗,那小程序提测、发版的效率必然能够得到提升。
我们可以稍加分析,看看哪些方面可以优化:
- 构建阶段
构建时间取决于我们项目的大小、复杂度以及依赖框架的性能,通常来说这个方向减少的是编译、开发的时间,此文我们不赘述。
- 上传阶段
上传阶段总时间 = 等待调试器时间 + 上传时间
小程序上传时间取决于构建产物的大小和用户等待打开调试器的时间【假定网速恒定】,这其中构建产物的大小和构建时间一样取决于项目,我们能减少的就是我们消耗在等待小程序的时间。
- 设置阶段
在设置阶段,我们需要机械且重复的扫码登录微信小程序后台,找到自己上传的版本,设置成体验版,这个流程很重复,有着明显的优化空间。
可以看出,我们在发版和提测阶段主要能优化的就是一些等待调试器、登录后台、手动设置体验版等需要切换且重复的过程。而解决这些问题的方案,就是小程序自己的解决方案:小程序 CI
小程序 CI
miniprogram-ci 是从微信开发者工具中抽离的关于小程序/小游戏项目代码的编译模块。
开发者可不打开小程序开发者工具,独立使用 miniprogram-ci 进行小程序代码的上传、预览等操作。
通过官方文档的描述,我们可以知道小程序 CI 的最大功能就是能让我们完全摆脱调试工具,直接通过命令行或者脚本进行小程序代码的上传操作,这将彻底解决我们在 上传阶段 和 设置阶段 提到的问题。
How to start?
因为我们小程序使用的框架为 Taro,且 Taro 官方提供了接入小程序 CI 的插件:@taro/plugin-mini-ci,所以我们接下来的流程都是基于该插件进行的。
获取小程序上传密钥
设置路径:开发 -> 开发管理 -> 开发设置 -> 小程序代码上传
设置小程序 IP 白名单
在上图中开启 IP 白名单并新增 IP 即可,图中不开启的原因是因为动态 IP,导致无法固定上传 IP 导致。
taro-mini-ci 配置
参考 taro-docs.jd.com/taro/docs/p… 即可。
完成上述步骤,我们就获得了三个 Taro 的命令行选项:
- --open 打开开发者工具
- --upload 上传代码作为体验版
- --preview 上传代码作为开发版并生成预览二维码
可是这只是将我们的上传过程简化了,我们还是需要配置一些版本和描述信息,才能完成打包和上传的全流程。而且每次都需要手动变更,这显然不是一个足够优秀的方案,我们还可以更进一步。
使用命令行
我们期望的是最好能在命令行里处理一切上传相关的事务,完全不需要对代码做出任何更改,那我们需要做到哪些事情才能完全交给命令行处理一切呢?
我们需要知晓需要发布的版本和版本描述:我们期望能直接在命令行中知道用户需要发布的版本和版本描述,显然这需要我们通过命令行来进行询问(还记得我们使用各类框架的 cli 时让我们选择的选项吗)。
只有知道了版本和版本描述,我们才能修改对应的 Taro 配置来发布版本,所以我们需要一些小工具来帮助我们:
获取用户的输入
inquirer
A collection of common interactive command line user interfaces.
Inquirer 是一个命令行与用户进行交互的工具集,通过 inquirer 我们可以询问用户的需求,并将其作为接下来的参数放到脚本中进行调用。
const inquirer = require('inquirer');
inquirer
.prompt([
{
type: 'input',
name: 'version',
message: '请输入要发布的版本号',
},
{
type: 'input',
name: 'desc',
message: '请输入上传描述文案',
},
]).then(({version, desc}) => {
// ...do something with version & desc
})
上面的这个例子就是简单地 inquirer 使用,通过上述脚本,我们可以在 Promise.then 中取到用户输入的版本号和描述文案。
版本的处理
在上述的例子里,我们通过让用户直接输入版本号来确定我们需要发布的版本,但是这需要用户知道上一个版本是多少,然后才能根据需求确定自己的下一个版本号是什么,这似乎有涉及到了需要用户手动计算的部分(虽然只是版本号)。
为了减少这部分需要用户确认和计算的时间,我们可以参考小程序工具本身的做法:
小程序提供了一个 prompt 通过匹配其线上的版本,然后通过三种类型来自动计算新的版本,虽然我们无法从线上获取版本号,但是我们却可以将发布的版本固化到本地然后通过类似的类型来实现这个流程。
const inquirer = require('inquirer');
const packageJson = require('../package.json');
const processMiniVersion = (type, version) => {
switch (type) {
case 'major': {
const [major, feat, fix] = version.split('.');
return `${Number(major) + 1}.${feat}.${fix}`;
}
case 'feature': {
const [major, feat, fix] = version.split('.');
return `${major}.${Number(feat) + 1}.${fix}`;
}
case 'fix': {
const [major, feat, fix] = version.split('.');
return `${major}.${feat}.${Number(fix) + 1}`;
}
}
};
inquirer
.prompt([
{
type: 'list',
name: 'type',
message: '更新类型',
choices: [
// 版本升级
'major',
// 特性更新
'feature',
// bug修复
'fix',
],
},
{
type: 'input',
name: 'desc',
message: '请输入上传描述文案',
},
])
.then(({ type, desc }) => {
// package.json中固定 taroConfig: { version: 'x.x.x'}
const prevVersion = packageJson.taroConfig.version
const currentVersion = processMiniVersion(prevVersion)
// ...do something with currentVersion & desc
});
基于上面的处理,我们已经能够通过命令行的交互自动根据本地固化的版本号来获取用户需要发布的版本和描述信息,这些已经足够我们完全脱离小程序开发者工具进行版本的上传和体验版的设置(只要将 CI 机器人的版本设置为体验版就再也无需变动)
Git tag 的加入
固定到本地的版本可以方便我们发布版本,但是却带来了另一个问题:只有经过计算我们才能知道下一个版本是多少,这就导致了执行完了我们的上传脚本之后我们还需要把更新了版本的本地文件合并到主分支。
所以我们需要找到一个不需要本地文件的方案,而 git tag 则刚好匹配我们的需求,为了接入 Git tag 我们的上传流程也需要调整:
- 上传前通过命令行传入 git 中最新的 tag
- 通过用户的交互计算需要上传的版本
- 根据上传版本和描述上传代码
- 根据需求确定是否把上传版本打上 tag 上传到 git 供下一次使用。
获取最新的 git tag
git describe --tags $(git rev-list --tags --max-count=1)
git describe --tags: 通过 git describe 我们可以查找整个 git 的 refs/tags 命名空间下的对应 commit 的 tag 的 name。
git rev-list --tags --max-count=1: 该命令行会将 refs/tags 命名空间下的所有 tags 的 commit 按照时间倒序排列,而因为有最大值限制,相当于我们通过该命令行取到的是最近一个 tag 的最后一个 commit.
通过这两个命令行的协作,我们可以拿到当前项目最新的 tag 的 name,也就是我们每次发版完 push 到 git 的小程序版本号。而将这个版本号通过命令行参数传递给我们的脚本,我们就不需要再依赖本地文件中的版本号,也不再需要更改本地文件。
将 git tag 应用于脚本
我们可以通过 cli 工具或者自行解析 node 的参数来识别传入的 git tag
// 假设调用的命令行为 node scripts/upload.js --version 'git describe --tags $(git rev-list --tags --max-count=1)'
const inquirer = require('inquirer');
const cli = require('cac')();
cli
.option('--version <version>', 'Get current version', {
default: '0.0.0',
});
const processMiniVersion = (type, version) => {
switch (type) {
case 'major': {
const [major, feat, fix] = version.split('.');
return `${Number(major) + 1}.${feat}.${fix}`;
}
case 'feature': {
const [major, feat, fix] = version.split('.');
return `${major}.${Number(feat) + 1}.${fix}`;
}
case 'fix': {
const [major, feat, fix] = version.split('.');
return `${major}.${feat}.${Number(fix) + 1}`;
}
}
};
inquirer
.prompt([
{
type: 'list',
name: 'type',
message: '更新类型',
choices: [
// 版本升级
'major',
// 特性更新
'feature',
// bug修复
'fix',
],
},
{
type: 'input',
name: 'desc',
message: '请输入上传描述文案',
},
]).then(({type, desc}) => {
const {
options: { version },
} = cli.parse();
const currentVersion = processMiniVersion(type, version);
// ...do something with currentVersion & desc.
});
确定是否需要将 tag 上传到 git
在这一步,我们只需要根据我们自身的需求来确定是否要将新的版本号 push 到 git ,这取决于我们是否真的发版,开发者需要自行在脚本里面根据环境或者参数来决定。