目录
一、前言
二、小程序运行机制
三、开发阶段
1. 跨平台框架选型
2. 包体积优化
2.1 主包/分包配置
3. 分仓开发
4. 提效工具
4.1 API环境切换功能
4.2 DEV面板
5. 自动化发布
四、发布后的数据分析
1. 稳定性监控
2. JS错误分析
五、总结思考
一
前言
提起微信小程序,相信所有人都不陌生,下面这个典型使用场景你一定经历过:
餐馆落座——微信扫桌角小程序码——使用微信小程序点餐🍔
微信小程序(下文简称:小程序)作为一种在微信平台内运行的应用程序,用户无需前往应用商店下载安装包即可使用,可以在微信内被便捷地获取和传播,2017年一经推出便迅速成为热门技术关键词,得物也随即发布了得物App小程序,欢迎扫码体验:
七年时间过去了,小程序的周边配套已经十分成熟,微信官方对小程序生态进行了很多迭代——微信开发者工具从难用到可用、小程序API经历了多轮简化和拓展、数据分析能力逐渐完善,同时开源社区也贡献了非常多的开发框架、实践记录等。
得物内部也形成了自己的小程序开发生态,本文主要介绍一些得物App小程序版本迭代中形成的开发指南、实践经验等,给小程序新手开发者一些实践经验,如果你已经是个小程序开发高手了,那么欢迎交流指正我们做得好的/不好的部分。
事实上,“小程序”这种技术架构并非微信首创,但无疑微信小程序是目前生态建设最完整、用户量最大的小程序生态。
二
小程序运行机制
先简单介绍一下小程序的运行机制和架构设计。
小程序的架构设计初衷是“快”,同时又需要对展示内容做严格的安全管控,所以采用了Webview结合Native的Hybrid技术,设计了双线程模型架构:
渲染层:小程序界面由Webview渲染,一部分较复杂组件用客户端原生渲染,以提供更好的性能。
逻辑层:一个JavaScript沙箱环境(利用原生客户端系统的JavaScript的解释引擎创建),只执行有关小程序业务逻辑的代码,不能访问任何浏览器相关接口,以避免恶意代码运行:在运行时访问DOM树获取敏感数据、跳转到其他在线网页等。
上述渲染层和逻辑层只是上层的抽象概念,在不同运行环境下有各自技术实现,如下表:
小程序本质上是Web和Native的结合——成熟的Web渲染技术负责界面渲染、Native提供客户端原生API补充Web的短板;由于Web基于单线程模型且开放灵活,因此微信隔离了渲染层与逻辑层,以此作为对内容安全管控的有效控制手段。
目前小程序在渲染层上已经有了基于"Skyline渲染线程"的新型架构,减少了双线程架构之间的通信开销和内存占用,理论上拥有更好的性能,需要单独开启,可自行查阅相关文档。
三
开发阶段
从小程序的运行机制可以得知:小程序在开发、发布、运行等每个环节都依赖于微信自身的生态而非传统Web,所以有很多特有的设计。先介绍一些基础概念和知识:
用户端运行的小程序资源文件需要先上传到微信侧,然后才能推送到用户端,微信限制了资源体积大小:
整个小程序所有分包大小不超过20M(开通虚拟支付后的小游戏不超过30M);
单个分包/主包大小不能超过2M。
主包:小程序默认启动页面/TabBar页面的构建产物,以及一些所有分包都需用到公共资源/JS脚本;
分包:为了提高初始化加载速度,开发者可以划分以页面为维度的分包,分包只包含一些子级页面的代码。
跨平台框架选型
如果你的项目确实只需要运行在微信小程序平台,选用微信小程序原生开发方式或许是一个更好的选择——能随时接入微信小程序的最新特性和API,缺点就是不够灵活,如果日后还想要在别的平台开展相同的业务,需要额外开发工作。
更普遍的情况是,大多数开发者都选用跨端框架去开发小程序,毕竟程序员都喜欢 Write once, run anywhere,所以支持小程序端的跨平台框架在小程序刚推出的前几年如雨后春笋般层出不穷。
当然,截止到2024这个时间节点,其中很多框架都已经停止维护(比如remax、mpvue、wepy...),目前比较推荐的跨平台框架有:uni-app、Taro,两者异同比较:
一句话总结:喜欢写Vue的话用uni-app,喜欢写React用Taro,这两者在小程序平台上的表现来说都没有绝对的优势和劣势,社区活跃度都很高,时至今日都在持续更新维护中。
目前在得物内部,uni-app和Taro都有使用。
包体积优化
主包/分包配置
从前文中可以得知,微信限制了单个分包/主包大小不能超过2M,因此需要规划合理的目录,以uni-app的开发目录做示例:
`├── ...``├── bin // 构建命令``├── loaders``├── src``│ ├── assets // 静态资源``│ ├── components // 通用组件``│ ├── pages // 主包目录``│ ├── product // product分包目录``│ ├── order // order分包目录``│ ├── ...``│ ├── sdk // 外部SDK``│ ├── store // 全局状态``│ ├── style // 样式文件``│ ├── utils // 通用方法``│ └── wxcomponents // 小程序原生组件``├── ...`
小程序根目录下的app.json文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多tab等。(选用不同的跨平台框架会有不同的配置方式和语法,但是殊途同归,编译到小程序平台的配置文件都会遵从小程序规范,下文将用小程序原生语法做说明。)
需要额外注意的是,考虑到小程序有主包和分包的设计,将关联性较强的页面放在同一目录里可以更合理地配置分包,示例如下:
`// app.json``{` `// 主包页面` `"pages": [` `"pages/index/index",` `"pages/xxxxx/index",` `],` `// 分包配置页面` `"subPackages": [` `{` `"root": "product",` `"pages": [` `"xxxxx/index",` `]` `},` `],` `"window": {` `// 全局相关配置` `},` `"tabBar": {` `// tabBar相关配置` `}``}`
即便是合理地配置了分包且做好了目录规划,也依然可能超过2M的体积限制,这个时候我们的优化手段跟Web项目基本相同,这里不再赘述,主要手段:静态资源从cdn加载、代码压缩、依赖优化分析等。
分仓开发
小程序的构建发布机制决定了所有页面都要一起打包、上传,在开发阶段也是如此。
我们曾经真实经历的开发流程:
打开IDE,打开项目代码;
运行微信平台构建命令:npm run mp-weixin;
运行字节平台构建命令:npm run mp-bytedance;
运行QQ平台构建命令:npm run mp-qq;
运行Web构建命令:npm run h5;
打开微信开发者工具、字节开发者工具、QQ开发者工具、Chrome浏览器,在不同工具中预览页面。
上古时期的噩梦
我们的小程序业务代码已经非常庞大,面对这样复杂的多线程开发流程,开发阶段的编译构建给研发电脑极大的内存压力,卡顿属实家常便饭。
为了能更高效地进行开发工作,我们利用Webpack的虚拟模块能力,结合私有npm包管理,将业务代码收敛进了各业务域的仓库内,做到了:
将一个完整的小程序项目,分割成多个能独立进行开发、部署的子应用(基于独立git仓库),同时又可以将所有子应用作为一个完整小程序应用打包发布。
时至今日,分仓开发在得物成为了“过去式”——由于后续的一波研发电脑大升级(唯快不破M2 Pro+力大砖飞32GB RAM),同时得物在QQ平台&字节平台的小程序也停止了迭代。只运行Web构建和小程序构建,并不会卡顿影响开发效率,所以目前已经不再需要分仓开发模式。
限于本文主题和篇幅,这里我们不再过多赘述分仓开发相关实现,有兴趣的话记得关注我们,后续可以整理一波技术文档以供方案参考~
提效工具
在Web中切换API环境有天然的域名隔离,可以直接访问不同域名下部署的站点。而在小程序中,像原生App一样,页面路径在用户端是无感且不能修改的,而且小程序没有安装包的概念,同一时间只存在一个体验版。所以为了更方便去调试和测试,可以做一些提效小工具。
API环境切换功能
在TabBar页面可以开发一个环境切换功能,例如:
`<view v-if="!IS_PRODUCTION" class="changeServiceEnv">` `<view class="changeTitle">环境切换</view>` `<radio-group @change="radioChange">` `<radio` `v-for="item in ENV_Array"` `:key="item"` `:value="item"` `:checked="item === SERVICE_ENV"` `class="radio-info"` `>` `{{ item }}` `</radio>` `</radio-group>` `</view>`
`radioChange(e) {` `let { value } = e.detail` `this.$store.commit('SET_SERVICE_ENV', value)` ``uni.showToast({ title: `当前环境是${this.SERVICE_ENV}` })```}`
这样就可以做到在单一体验版中自由切换API环境,验证不同环境下的数据展现和业务逻辑了。
DEV面板
在测试场景下,尝试访问路径较深的页面会比较耗时繁琐,和很多原生App的测试包做法一样,可以开发一个全局的DEV面板。
但是受限于小程序的架构设计:小程序每跳转到一个页面,实际上都是打开一个新的Webview,在这种设计架构下,虽然可以注册全局组件,但是如果想要在每个页面都渲染出全局组件,还是需要在每个页面都写一遍全局组件的代码,这样并不cool,我们探索出来的方案:
在非生产环境的Webpack配置中使用注入全局组件的loader:
分析小程序页面配置,计算并得到路由表,这一步具体怎么实现跟选用的框架相关;
loader的上下文环境this.resourcePath指向真实的文件路径,将它和上一步拿到路由表进行匹配,如果未命中,则不做任何操作;
分析命中路由表的文件,从中找到模版代码,紧随第一个标签,在其后方插入全局组件代码:
这一步可以用正则,也可以用AST;
对于我们的项目来说,在.vue文件的