听说你还在用开发者工具手动上传小程序,快来试试 miniprogram-ci 提效摸鱼

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信进群。

本文是 @NewName 小伙伴参加源码共读活动第30期(@tarojs/plugin-mini-ci 小程序上传代码 ci)的投稿。

原文链接:https://juejin.cn/post/7089819849257385997

此人非同寻常,我发布了多少期源码共读、他就基本写了多少期文章。

Part1学习准备工作

阅读相关学习资料:

微信小程序CI :https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html
taro CI: https://link.juejin.cn/?target=https%3A%2F%2Ftaro-docs.jd.com%2Ftaro%2Fdocs%2Fnext%2Fplugin-mini-ci%2F
coding自动构建微信小程序:https://help.coding.net/docs/ci/practice/quick/wechat-mini-program.html
小打卡小程序自动化构建:https://www.yuque.com/jinxuanzheng/gvhmm5/uy4qu9#mmmx7

clone源码:源码路径如下:

https://github.com/NervJS/taro/tree/next/packages/taro-plugin-mini-ci

我是把taro整个clone下来的。

Part2源码学习

1小程序CI的整体流程

首先看index.js:

module.exports = require('./dist/index.js').default
module.exports.default = module.exports

含义是引入dist文件夹下的index.js文件, 但是刚克隆下来的代码中并没有dist文件夹。很容易想到dist文件夹是打包后产生的,所以执行命令:

npm i
npm run build

注意是在taro/packages/taro-plugin-mini-ci目录下执行 install和build命令:43ca431666abf8d0f3da3fb914fce102.pngbuild之后可以看到有了dist文件夹:1cf0d3eaf792650ab1bcd18d85396b80.png对应目录下也生成了index.js文件,生成的js文件和原来的ts文件也没差太多,再加上最近再自学ts,就看index.ts吧(代码有删减):

import { IPluginContext } from '@tarojs/service'
import * as minimist from 'minimist'
import { CIOptions } from './BaseCi'
import WeappCI from './WeappCI'
import TTCI from './TTCI'
import AlipayCI from './AlipayCI'
import SwanCI from './SwanCI'export { CIOptions } from './BaseCi'
export default (ctx: IPluginContext, pluginOpts: CIOptions) => {const onBuildDone = ctx.onBuildComplete || ctx.onBuildFinishctx.addPluginOptsSchema((joi) => {return joi.object().keys({/** 微信小程序上传配置 */weapp: joi.object({appid: joi.string().required(),projectPath: joi.string(),privateKeyPath: joi.string().required(),type: joi.string().valid('miniProgram', 'miniProgramPlugin', 'miniGame', 'miniGamePlugin'),ignores: joi.array().items(joi.string().required())}),/** 字节跳动小程序上传配置 *//** 阿里小程序上传配置 *//** 百度小程序上传配置 */swan: joi.object({token: joi.string().required(),minSwanVersion: joi.string()}),version: joi.string(),desc: joi.string()}).required()})onBuildDone(async () => {const args = minimist(process.argv.slice(2), {boolean: ['open', 'upload', 'preview']})const { printLog, processTypeEnum } = ctx.helperconst platform = ctx.runOpts.options.platformlet ciswitch (platform) {case 'weapp':ci = new WeappCI(ctx, pluginOpts)breakcase 'tt':ci = new TTCI(ctx, pluginOpts)breakcase 'alipay':case 'iot':ci = new AlipayCI(ctx, pluginOpts)breakcase 'swan':ci = new SwanCI(ctx, pluginOpts)breakdefault:break}if (!ci) {printLog(processTypeEnum.WARNING, `"@tarojs/plugin-mini-ci" 插件暂时不支持 "${platform}" 平台`)return}switch (true) {case args.open:ci.open()breakcase args.upload:ci.upload()breakcase args.preview:ci.preview()breakdefault:break}})
}

代码的整体流程比较简单,判断平台,创建CI实例, 执行对应的CI。6b418ec16b37cd5e54f3d632b2d466b3.png

可以在启动Node.js 程序时直接指定命令行参数,例如:

node index.js --beep=boop -t -z 12 -n5 foo bar

Node.js 程序启动后可以直接从process.argv中读取到参数列表:

console.log(process.argv);
// ['/bin/node', '/tmp/index.js', '--beep=boop', '-t', '-z', '12', '-n5', 'foo', 'bar']

从上述代码中可以看到,process.argv 变量是一个数组,数组前两项分别是 node 程序位置和js脚本位置,数组中随后的元素都是我们启动Node.js后的参数,这些参数以空格分隔成数组。而minimist 是一个专门用于处理Node.js启动参数的库,可以将 process.argv 中的参数列表转换成更加易于使用的格式:

const argv = require('minimist')(process.argv.slice(2));
console.dir(argv);
// { _: [ 'foo', 'bar' ], beep: 'boop', t: true, z: 12, n: 5 }

具体使用可以参考https://www.npmjs.com/package/minimist, 使用的时候接收参数和配置对象。

2CI抽象类:BaseCI

packages/taro-plugin-mini-ci/src/BaseCi.ts(代码有删减):

import { IPluginContext } from '@tarojs/service'
import * as path from 'path'export type ProjectType = 'miniProgram' | 'miniGame' | 'miniProgramPlugin' | 'miniGamePlugin';/** 微信小程序配置 *//** 头条小程序配置 *//** 支付宝系列小程序配置 *//** 百度小程序配置 */export interface CIOptions {/** 发布版本号,默认取 package.json 文件的 taroConfig.version 字段 */version: string;/** 版本发布描述, 默认取 package.json 文件的 taroConfig.desc 字段 */desc: string;/** 微信小程序CI配置, 官方文档地址:https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html */weapp?: WeappConfig;/** 头条小程序配置, 官方文档地址:https://microapp.bytedance.com/docs/zh-CN/mini-app/develop/developer-instrument/development-assistance/ide-order-instrument */tt?: TTConfig;/** 支付宝系列小程序配置,官方文档地址:https://opendocs.alipay.com/mini/miniu/api */alipay?: AlipayConfig;/** 百度小程序配置, 官方文档地址:https://smartprogram.baidu.com/docs/develop/devtools/commandtool/ */swan?: SwanConfig;
}export default abstract class BaseCI {/** taro 插件上下文 */protected ctx: IPluginContext/** 传入的插件选项 */protected pluginOpts: CIOptions/** 当前要发布的版本号 */protected version: string/** 当前发布内容的描述 */protected desc: stringconstructor (ctx: IPluginContext, pluginOpts: CIOptions) {this.ctx = ctxthis.pluginOpts = pluginOptsconst { appPath } = ctx.pathsconst { fs } = ctx.helperconst packageInfo = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json'), {encoding: 'utf8'}))this.version = pluginOpts.version || packageInfo.taroConfig?.version || '1.0.0'this.desc = pluginOpts.desc || packageInfo.taroConfig?.desc || `CI构建自动构建于${new Date().toLocaleTimeString()}`this._init()}/** 初始化函数,会被构造函数调用 */protected abstract _init():void;/** 打开小程序项目 */abstract open();/** 上传小程序 */abstract upload();/** 预览小程序 */abstract preview();
}

在抽象类中定义了一些属性是protected的,意味着可以在本类以及子类中访问;在constructor中对属性进行了初始化,并调用初始化函数。然后是定义了一些CI操作的抽象方法。

3CI子类:AlipayCI

packages/taro-plugin-mini-ci/src/AlipayCI.ts

/* eslint-disable no-console */
import * as miniu from 'miniu'
import * as path from 'path'
import BaseCI from './BaseCi'
import generateQrCode from './QRCode'/** 文档地址:https://opendocs.alipay.com/mini/miniu/api */
export default class AlipayCI extends BaseCI {protected _init (): void {if (this.pluginOpts.alipay == null) {throw new Error('请为"@tarojs/plugin-mini-ci"插件配置 "alipay" 选项')}const { appPath } = this.ctx.pathsconst { fs } = this.ctx.helperconst { toolId, privateKeyPath: _privateKeyPath, proxy } = this.pluginOpts.alipayconst privateKeyPath = path.isAbsolute(_privateKeyPath) ? _privateKeyPath : path.join(appPath, _privateKeyPath)if (!fs.pathExistsSync(privateKeyPath)) {throw new Error(`"alipay.privateKeyPath"选项配置的路径不存在,本次上传终止:${privateKeyPath}`)}miniu.setConfig({toolId,privateKey: fs.readFileSync(privateKeyPath, 'utf-8'),proxy})}open () {const { printLog, processTypeEnum } = this.ctx.helperprintLog(processTypeEnum.WARNING, '阿里小程序不支持 "--open" 参数打开开发者工具')}async upload () {const { chalk, printLog, processTypeEnum } = this.ctx.helperconst clientType = this.pluginOpts.alipay!.clientType || 'alipay'printLog(processTypeEnum.START, '上传代码到阿里小程序后台', clientType)// 上传结果CI库本身有提示,故此不做异常处理// TODO 阿里的CI库上传时不能设置“禁止压缩”,所以上传时被CI二次压缩代码,可能会造成报错,这块暂时无法处理; SDK上传不支持设置描述信息const result = await miniu.miniUpload({project: this.ctx.paths.outputPath,appId: this.pluginOpts.alipay!.appId,packageVersion: this.version,clientType,experience: true,onProgressUpdate (info) {const { status, data } = infoconsole.log(status, data)}})if (result.packages) {const allPackageInfo = result.packages.find(pkg => pkg.type === 'FULL')const mainPackageInfo = result.packages.find((item) => item.type === 'MAIN')const extInfo = `本次上传${allPackageInfo!.size} ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}}async preview () {const previewResult = await miniu.miniPreview({project: this.ctx.paths.outputPath,appId: this.pluginOpts.alipay!.appId,clientType: this.pluginOpts.alipay!.clientType || 'alipay',qrcodeFormat: 'base64'})console.log('预览二维码地址:', previewResult.packageQrcode)generateQrCode(previewResult.packageQrcode!)}
}

支付宝小程序子类的_init()方法主要做参数的验证和设置;open,upload,preview实现了抽象类定义的方法,分别用于打开开发者工具,上传代码,预览二维码。核心功能的实现依赖于miniu。可以查看相应的资料。627ea6eb73df2b00180d6e704d01639b.png这篇文章介绍了使用MiniU完成CI/CD:https://forum.alipay.com/mini-app/post/35101018。生成二维码调用了generateQrCode方法:

/*** 生产二维码输出到控制台* @param url 链接地址*/
export default function generateQrCode (url: string) {require('qrcode-terminal').generate(url, { small: true })
}

generateQrCode实际上是通过三方包qrcode-terminal来实现的。

4CI子类:SwanCI

在SwanCI类中open方法和preview方法的实现与AlipayCI一样,upload实现有所不同:

async upload () {const { outputPath } = this.ctx.pathsconst { chalk, printLog, processTypeEnum } = this.ctx.helperprintLog(processTypeEnum.START, '上传体验版代码到百度后台')printLog(processTypeEnum.REMIND, `本次上传版本号为:"${this.version}",上传描述为:“${this.desc}”`)shell.exec(`${this.swanBin} upload --project-path ${outputPath} --token ${this.pluginOpts.swan!.token} --release-version ${this.version} --min-swan-version ${this.pluginOpts.swan!.minSwanVersion || '3.350.6'} --desc ${this.desc} --json`, (_code, _stdout, stderr) => {if (!stderr) {// stdout = JSON.parse(stdout)console.log(chalk.green(`上传成功 ${new Date().toLocaleString()}`))}})}

上传的时候执行shell脚本,通过shelljs来实现的 。

5CI子类:WeappCI

WeappCI主要是使用了miniprogram-ci ,具体看一下open, upload, preview方法:open方法(代码有删减):

import * as cp from 'child_process'async open () {const { fs, printLog, processTypeEnum, getUserHomeDir } = this.ctx.helperconst { appPath } = this.ctx.paths// 检查安装路径是否存在/** 命令行工具所在路径 */// 检查是否开启了命令行cp.exec(`${cliPath} open --project ${appPath}`, (err) => {if (err) {printLog(processTypeEnum.ERROR, err.message)}})}

open方法用于打开开发者工具,通过node.js child_process的exec执行命令。upload方法:

import * as ci from 'miniprogram-ci'async upload () {const { chalk, printLog, processTypeEnum } = this.ctx.helpertry {printLog(processTypeEnum.START, '上传体验版代码到微信后台')printLog(processTypeEnum.REMIND, `本次上传版本号为:"${this.version}",上传描述为:“${this.desc}”`)const uploadResult = await ci.upload({project: this.instance,version: this.version,desc: this.desc,onProgressUpdate: undefined})if (uploadResult.subPackageInfo) {const allPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__FULL__')const mainPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__APP__')const extInfo = `本次上传${allPackageInfo!.size / 1024}kb ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size + 'kb' : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}}

上传代码的方法使用miniprogram-ci的upload方法,得到结果信息后根据分包信息来提示整体包大小和主包大小。preview方法(代码有删减):

async preview () {const { chalk, printLog, processTypeEnum } = this.ctx.helpertry {printLog(processTypeEnum.START, '上传开发版代码到微信后台并预览')const uploadResult = await ci.preview({project: this.instance,version: this.version,desc: this.desc,onProgressUpdate: undefined})if (uploadResult.subPackageInfo) {const allPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__FULL__')const mainPackageInfo = uploadResult.subPackageInfo.find((item) => item.name === '__APP__')const extInfo = `本次上传${allPackageInfo!.size / 1024}kb ${mainPackageInfo ? ',其中主包' + mainPackageInfo.size + 'kb' : ''}`console.log(chalk.green(`上传成功 ${new Date().toLocaleString()} ${extInfo}`))}} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}
}

preview方法使用的是miniprogram-ci的preview方法, 得到结果信息后根据分包信息来提示整体包大小和主包大小。

6CI子类:TTCI

TTCI使用tt-ide-cli来完成预览和上传,使用child_process的exec来完成打开开发者工具的功能。open(代码有删除):

open () {if (fs.existsSync(projectPath)) {console.log(chalk.green(`open projectPath: ${projectPath}`))const openPath = `${openCmd}?path=${projectPath}`cp.exec(openPath, (error) => {if (!error) {console.log('打开IDE成功', openPath)} else {console.log(chalk.red('打开IDE失败', error))}})}}

这里open方法也是通过node.js child_process的exec执行命令。upload(代码有删除):

import * as tt from 'tt-ide-cli'
async upload () {try {await tt.upload({entry: outputPath,version: this.version,changeLog: this.desc})} catch (error) {}}

上传代码使用tt-ide-cli的upload方法。preview(代码有删除):

import * as tt from 'tt-ide-cli'async preview () {try {await tt.preview({entry: outputPath,force: true,small: true})} catch (error) {console.log(chalk.red(`上传失败 ${new Date().toLocaleString()} \n${error.message}`))}}

生成预览二维码使用了tt-ide-cli的upload方法。

Part3总结

1.taro小程序ci的核心代码逻辑是:判断平台,创建CI实例, 执行对应的CI。2.不同平台对应不同的CI类,但都继承了基础的CI抽象类,实现了抽象类声明的open,upload和preview方法。3.实现具体的open,upload和preview方法时根据对应小程序是否提供了命令行工具,有用到miniu,tt-ide-cli,miniprogram-ci,还有的使用shelljs,qrcode-terminal,以及child_process来执行命令。


cf714cd4d9dfebc36933cc7720c8b8ed.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助4000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

4347d3951e26844834b0a0afaa8676cf.png

扫码加我微信 ruochuan02、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群。分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274697.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ucla ai_UCLA的可持续性:用户体验案例研究

ucla aiRole: UX Researcher / UX Designer / Critical-thinker角色: UX研究人员/ UX设计人员/批判性思维者 Scope: 4 weeks, March — March 2020范围: 4周,2020年3月至2020年3月 What I Did: UX Research, Speculative Design, Product D…

大三的小白同学是如何拿到字节offer的,经验分享

这是来自大三邵小白同学的投稿。原文链接:https://juejin.cn/post/7092806181856657445很多时候我们容易羡慕别人成功了,却往往没有看到别人背后的努力。1前言大家好,我是邵小白,一个长沙某不知名双非的大三学生。今年三月份来到杭…

UNIBO大学博物馆网络设计—品牌重塑和数字产品设计

Brief / Redesign the Visual Identity of the University of Bologna Museum Network (SMA) and apply the new designs to a Digital Product简介/重新设计博洛尼亚大学博物馆网络(SMA)的视觉识别,并将新设计应用于数字产品 Period / Mar 2020 — June 2020期间/…

进来做几道 JavaScript 基础题找找自信?

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

人物肖像速写_骄傲家庭:肖像项目

人物肖像速写2020 has been a solemn, transformative year. Pride month takes place in the context of a groundswell up-rising against racism and police brutality and in the continued isolation of COVID-19.2020年是庄严,变革的一年。 骄傲月的发生是在反…

答读者问:钱和成长,哪个更重要?

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

ui设计颜色的使用_UI设计中颜色使用的10条原则

ui设计颜色的使用重点 (Top highlight)1.颜色术语 (1. Color Terminology) Color terminology forms our foundation of color knowledge. Think of color terms like hue, tint, and shade as tools that we can employ to develop unique color palettes.颜色术语构成了我们颜…

Chrome插件:网易云音乐听歌识曲

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

如何设置ad18捕捉图标_图标设计中的像素捕捉

如何设置ad18捕捉图标More in the iconography series:• Foundations of Iconography• 7 Principles of Icon Design• 5 Ways to Create a Settings Icon• Icon Grids & Keylines Demystified• 3 Classic Icon FamiliesWe all want our designs to display sharp on a…

React Hooks 原理与最佳实践

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

插图 引用 同一行两个插图_为什么插图是产品的重要组成部分

插图 引用 同一行两个插图“Hi, my name is Ludmila and I’m a UX/UI designer”“嗨,我叫Ludmila,我是UX / UI设计师” “Hi, Ludmila”“嗨,路德米拉” “Welcome”“欢迎” Not anonymously at all, I’ve been doing UX/UI design fo…

如果是你你会如何重新设计和定义维基百科(wikipedia)?

日期:2012-8-11 来源:GBin1.com 最近一家设计公司发布了一个关于如何重新定义和设计维基百科的网站,在这里网站里详细的刨析了如何重新设计维基百科的话,如何做品牌设计和网站设计,整个设计过程都使用非常详细的文档说…

祖父元素_帮助祖父母建立Skype帐户的UX经验教训

祖父元素“Empathy is a key part of a UX designers arsenal”, they say. It’s drilled into our heads that we need to be thinking about our user, about their journey, about what works best for them. And it does feel empowering to boast of empathy, inside vis…

2022年CSS的发展如何?

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

分布式实物实现方式_这是您完成实物产品设计任务的方式

分布式实物实现方式You’ve come to the last stages of an interview. There’s only one thing left to do: the dreaded take home design assignment.您已经到达面试的最后阶段。 只剩下一件事要做: 可怕的带回家的设计任务。 This is the hard part of any in…

type 和 interface 傻傻分不清楚?

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

上帝公式_感谢上帝的空白

上帝公式Do you ever walk into a room cluttered with discarded papers and leftover takeout and feel comfortable?您是否曾经走进过乱七八糟的房间? Yes, you might if you’re a sophomore at college. That’s just dorm life. Back in the late 90’s to …

figma下载_在Figma上进行原型制作的各种触发选项

figma下载Prototypes are model versions of digital products. They’re used to measure usability by testing with potential users of a product. When making prototypes with Figma, it is necessary that the actions that trigger reactions aren’t strangers and th…

通过动画让你深入理解 ES modules

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以 点此加我微信ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

海量数据处理之倒排索引

前言:本文是对博文http://blog.csdn.net/v_july_v/article/details/7085669的总结和引用 一,什么是倒排索引 问题描述:文档检索系统,查询那些文件包含了某单词,比如常见的学术论文的关键字搜索。 基本原理及要点&#…