uni-app开发微信小程序项目开发说明(vue3+ts)
项目构建及工程化说明
- 初始化uni-app项目,vue3+ts:
npx degit dcloudio/uni-preset-vue#vite-ts my-vue3-project
(如命令行创建失败,请直接访问 gitee下载模板)
添加.gitignore / .editorconfig 文件配置 (略)
引入eslint及prettier规范项目代码 (略)
引入微信小程序相关ts类定义 npm install @types/wechat-miniprogram
自动化UT测试搭建 参考cli项目自动化测试 及vitest使用说明。
a. 当前的测试方案使用的Vitest(vue3与vite的生态配合较好)。项目自动化测试仅支持单元测试。对于组件测试,目前uni-app并无文档支持,在使用@vue/test-utils尝试组件测试时,依赖的@dcloudio包会报错,怀疑是uni-app的编译过程对vue的运行环境进行了包装。 对于E2E测试,需要配合HbuilderX或微信开发者工具,无法在流水线上做自动化测试,暂未进一步研究。
b. 实际项目中对src下的所有ts文件中的业务函数进行测试用例覆盖,vue3引入了组合式函数,可以将绝大部分业务逻辑的处理提取到vue文件之外复用。数据及业务逻辑基本可以被UT测试覆盖到。网络请求和依赖的环境api,通过addTestingRecord/mockWxApi的方式构造场景,完成用例验证。
c. 基于第2点,vue文件里的ts代码,仅与视图及交互相关。核心的业务代码逻辑,请以组合式函数的方式提取到service层。
持续集成的基本指令 参考uni-app持续集成 当前使用的vue-cli方式打包。 项目本身支持使用HBuilderX cli打包及发行。但无法在流水线上使用。其依赖HBuilderX软件的安装,因此使用npm指令打包归档。然后通过。
# 编译 npm i npm run test:vite npm run build:mp-weixin packageName=${WORKSPACE_SERVICENAME}_$versionNumber # 打包 cd dist/build/mp-weixin/ zip -r $packageName.zip ./*
微信小程序上传发布 当前由开发人员,在本地通过IDE上传。该操作需要外网访问权限,由开发人员做信息安全备案后操作,微信小程序的上架,还需微信官方审核及运营人员做发布操作。 小程序协同工作和发布
使用vue-cli命令初始化项目的考虑:
- 使用HBuilderX开发工具初始化的代码,无法使用其他ide(vscode)开发uni-app。
- HbuilderX的开发体验相较Vscode较差, Vscode的开发使用范围更广,相关插件及工程开发的支持更好。
- 使用vue-cli初始化的代码,仍然可以通过HbuildX进行开发。
- 对于小程序及H5的开发场景,不依赖其内置的app及uniCloud相关的运行调试工具。
新人上手
- 开发工具安装:
安装微信开发者工具 (https://developers.weixin.qq.com/miniprogram/dev/devtools/devtools.html)
安装HBuilderX最新版本 (https://hx.dcloud.net.cn/)
安装 vscode最新版(https://code.visualstudio.com/)。 推荐vscode作为核心开发ide,开发体验更顺畅。
HbuilderX及vscode IDE内涉及部分插件安装,根据引导按需安装即可。
- 微信开发者工具配置:
- 扫码登录微信开发者工具, 需要有外网权限。 spes国内接入站点貌似无法联通,可尝试在海外接入站点切换。
- 点击“设置 =》 安全设置”,打开服务端口。 点击“设置 =》 代理设置”,选择使用系统代理。
- 点击“视图 =》 调试器”,打开调试器, 点击调试器视图上的 “Mock”,启用Mock。 (后续开发可mock部分请求,mock数据在项目的/mock目录下)
- HbuilderX开发启动:
- HbuilderX ide内导入项目目录
- 点击“运行 =》 运行到小程序模拟器 =》 微信开发者工具”
- 修改项目文件,保存后自动启动差量编译,并在微信开发者工具上预览实时效果。
- vscode开发启动:
- vscode ide内导入项目目录
- 执行指令 npm install
- 执行指令 npm run dev:mp-weixin
- 进入/dist/dev/mp-weixin目录,通过微信开发者工具导入该编译后的项目目录
- 修改项目文件,保存后自动启动差量编译,并在微信开发者工具上预览实时效果。
开发说明
项目基于vue 3开发。相关语法特性参见 uni-app教程; vue3 官网; vue3中,组合式api对typescript有更好的支持。
该项目当前使用uni-app模式进行开发,但仅涉及微信小程序形态。因此,项目中的http请求、登录、支付都使用的wx自有api。uni-app也内置了类型支持。后期如果需要基于此项目支持其他平台的开发,再在api/service层做适配。
vue3 + 组合式api + ts的开发生态下,vscode里有新的插件以支持更友好的开发, 请卸载Vetur插件以安装Volar
环境区分及联调:可以根据wx.getAccountInfoSync().miniProgram.envVersion区分微信小程序是开发版还是体验版还是发布版。以使用不同的环境配置
项目版本节奏说明
角色权限申请:
- 开发人员—— 申请 “开发者”角色
- 运营人员—— 申请 “运营者”角色
- 测试人员—— 申请 “体验人员”角色 (企业主体微信最多添加50名体验人员)
版本节点对应:
- 开发阶段——使用IDE本地开发,上传开发版本现网验证,开发版本仅开发人员可访问
- 版本转测——选择开发版本转为体验版本 (开发人员触发)
- 测试阶段——体验版本体验人员可访问,对接微信现网环境
- 众测发布——选择开发版本转为体验版本 (开发人员触发)
- 灰度发布——选择体验版本提交审核,由微信平台审核(运营人员触发)
- 灰度活动——提交分阶段发布(运营人员触发)
关键控制点: 灰度发布 和 灰度活动。 需要有项目组发布评审会的评估确认邮件,才能触发运营人员提交发布。
附 mock代码:
// mockHttpData.ts
type ResponseFun = (request?: any, header?: Map<string, string>) => HttpDataType.ApiResType;
interface MockRecord {
url: string;
method?: string;
response: HttpDataType.ApiResType | ResponseFun;
}
const baseMockData = [
{
url: 'urlpath',
response() {
return {
resultDesc: 'success',
resultCode: '000000',
} as HttpDataType.ApiResType;
},
}
] as Array<MockRecord>;
let testMockData = [] as Array<MockRecord>;
function existAndMatch(method1: any, method2: any) {
return !(method1 != null && method2 != null && method1 != method2);
}
export function resetTestingData() {
testMockData = [];
}
export function addTestingRecord(record: MockRecord) {
testMockData.unshift(record);
}
export function mockRequest(url: string, req: any, method?: string, header?: Map<string, string>): HttpDataType.ApiResType | null {
let apiData = null;
const mockData = [...testMockData, ...baseMockData];
for (const mockRecord of mockData) {
if (url.includes(mockRecord.url) && existAndMatch(method, mockRecord.method)) {
apiData = typeof mockRecord.response === 'function' ? mockRecord.response(req, header) : mockRecord.response;
break;
}
}
return apiData;
}
// mockApiData.ts
import { vi } from 'vitest';
import { mockRequest } from './mockHttpData';
type Record<K extends keyof any, T> = {
[P in K]: T;
};
const wx = {
getAccountInfoSync: () => {
return {
miniProgram: {
envVersion: 'develop',
},
} as WechatMiniprogram.AccountInfo;
},
login: async () => {
return {
code: new Date().getTime(),
};
},
request: async (requestOption: WechatMiniprogram.RequestOption<WechatMiniprogram.IAnyObject>,
) => {
const apiData = mockRequest(requestOption.url, requestOption.data, requestOption.method, requestOption.header as Map<string, string>);
if (apiData == null) {
const failData = { errno: -1, errMsg: 'this api( ' + requestOption.url + " ) doesn't mock, pls add mock data" };
console.error(failData);
requestOption.fail && requestOption.fail(failData);
}
const responseData = {
data: apiData,
statusCode: 200,
header: {
'content-type': 'application/json; charset=utf-8',
},
} as WechatMiniprogram.RequestSuccessCallbackResult<any>;
requestOption.success && requestOption.success(responseData);
},
} as Record<string, Function>;
vi.stubGlobal('wx', wx);
let changedApiRecords = new Map<string, Function>();
export function mockWxApi(methodName: string, apiFun: Function) {
if (!changedApiRecords.has(methodName)) {
changedApiRecords.set(methodName, wx[methodName]);
}
Object.defineProperty(wx, methodName, { value: apiFun });
vi.stubGlobal('wx', wx);
}
export function restMockWxApi() {
changedApiRecords.forEach((apiFun, methodName) => {
Object.defineProperty(wx, methodName, { value: apiFun });
});
changedApiRecords = new Map<string, Function>();
vi.stubGlobal('wx', wx);
}