作者:董宏平 (hiyuki)
Mpx是一款致力于提高小程序开发体验和效率的增强型小程序框架,目前在滴滴公司内部支撑了包括滴滴出行小程序,滴滴出行广场小程序,青桔单车,黑马电单车,小桔养车,小桔加油在内的小程序生态;自去年11月开源以来,Mpx也吸纳了众多外部开发者的加入,基于Mpx开发了开走吧,好免街,花忆等小程序。
长期以来,Mpx优秀的开发体验和强大的稳定性得到了内外开发者的一致认可和好评,这非常符合Mpx的设计初衷。但是在各大厂商陆续推出自己的小程序平台,且各家的技术标准都不统一的今天,单纯地提高某一个平台的开发体验已经不能满足广大小程序开发者们的诉求,一套代码在多小程序平台运行
已经成为一个现实上的刚需。为了解决这个小程序开发的痛点,Mpx发布了2.0版本,适配了目前业内已经发布的所有小程序平台(微信、支付宝、百度、头条、qq),并且提供了直接将现有微信小程序编译输出到其他平台运行的能力。
Mpx2.0版本新增的主要特性主要包含:
- 完整支持了目前业内已发布的所有小程序平台(微信,支付宝,百度,qq,头条);
- Mpx小程序跨平台开发,支持将已有的Mpx微信项目编译输出到其他已支持的小程序平台中运行,点击查看详情;
- 小程序原生组件跨平台编译,支持将已有的微信原生组件编译输出到其他已支持的小程序平台中运行;
- 深度分包优化,编译过程中进行精准分包资源判断,所有分包only的资源(组件、js、外部样式、外部模板、wxs,图像媒体等)都会精确输出到分包目录中;
- render函数中完整支持wxs模块,关于render函数点击查看详情;
- 支持了模板引入,内联wxs,自定义tabbar,独立分包,workers,云开发等原生能力,进一步完善原生兼容性。
同业内主流的小程序跨端框架相比,Mpx更专注于小程序开发本身,在小程序开发中具备以下优势:
- 基于小程序自身的技术标准进行增强,没有进行过重的DSL转换,开发时遇到的坑会更少;
- 完全兼容原生小程序技术规范,0成本迁移原生小程序项目;
- 跨平台开发以跨小程序平台为目标,大部分差异抹平工作在编译阶段进行,大大减少运行时适配层增加的包体积;
- 支持业内微信小程序组件库(如vant、iView等)直接转换到其他小程序平台运行;
- 非常重视小程序性能,提供了深度的setData和包体积优化。
Github:https://github.com/didi/mpx
跨平台开发
作为2.0版本的核心能力,Mpx的跨平台开发能力允许用户直接将已有小程序项目编译输出到其他已支持的小程序平台中运行。微信小程序作为小程序概念的提出者,有着最广泛的生态覆盖,因此我们优先支持了将微信小程序编译为其他平台小程序的能力。基于这个能力,用户不仅能跨平台编译微信Mpx项目,甚至能够将微信的原生自定义组件也编译到其他小程序平台进行运行,这意味着我们的跨平台项目能够直接使用一些社区内已有的UI组件库生态(如vant、iView等),极大地提高了跨平台开发的适用范围。
设计理念
Mpx框架的核心设计理念在于增强,增强是指在小程序已有的原生能力基础上做加法,拓展小程序的开发能力,提高小程序的开发体验和效率。这个设计理念使Mpx给开发者带来了更强的确定性和可预期性,更低的学习上手和调试成本。基于这个理念,Mpx在不同的小程序平台中进行了差异性的增强适配,并参考各个平台的模板指令风格提供了不同的增强模板指令集,让用户在各小程序平台中都可以以增强的方式去最大限度地使用平台自有的原生能力。
我们在对Mpx提供跨平台能力的支持时也遵循了增强的核心设计理念。简单来讲,Mpx的跨平台能力是在多平台能力的基础上,在编译和运行时增加了一层转换层,将源平台的代码转换为目标平台的代码之后,再按照既有的目标平台的处理逻辑进行增强,同时我们也提供了一套完善的条件编译机制,让用户自行实现少数框架无法转换的部分。
Mpx跨平台开发流程示意图
Mpx跨平台能力设计思路明显区别于业内已有的其他小程序跨平台框架,主要差异在于:
- Mpx以小程序本身的DSL作为基准,而没有使用web框架(React,Vue)的DSL;
- Mpx主要通过编译和运行时转换的方式处理平台差异,没有提供额外的差异抹平层(基础组件库等)。
之所以采用这种设计,主要基于以下原因:
- Mpx主要以跨小程序平台为目标,目前各大小程序平台的技术规范具有一定相似性,绝大部分平台差异能够通过编译和运行时手段抹平,同时省去的差异抹平层也能够进一步减少框架运行时体积;
- 使用小程序本身的DSL作为基准允许用户直接在已有项目中使用跨平台能力,对于原生小程序项目或组件也能够使用该能力进行跨平台输出;
- 结合完善的条件编译支持,该方案能够在满足用户跨平台需求的同时仍然允许用户最大限度地使用各个小程序平台提供的能力,完全延续了Mpx增强的核心设计理念。
使用方法
Mpx跨平台开发的使用方式非常简单,用户只需在MpxWebpackPlugin创建时传入mode和srcMode参数指定源平台和目标平台,当srcMode和mode不一致时,框架会读取相应的配置对项目进行编译和运行时转换。
// 微信转支付宝
new MpxWebpackPlugin({
// mode指定目标平台,可选值有(wx|ali|swan|qq|tt)
mode: 'ali',
// srcMode指定源码平台,默认值同目标平台一致
srcMode: 'wx'
})
差异抹平
目前各大厂商的小程序技术规范在宏观层面上大致保持一致,但是技术细节方面存在很多差异,大致划分为以下几个部分:
- 模板语法/基础组件差异
- json配置差异
- wxs语法差异
- 页面/组件对象差异
- api调用差异
- webview bridge差异
其中,对于模板语法/基础组件、json配置和wxs中的静态差异,我们主要通过编译手段进行转换处理,对于这部分差异中无法转换的部分会在编译阶段报错指出;而对于页面/组件对象、api调用和webview bridge中js运行时的差异,我们主要通过运行时手段进行处理,对应的无法转换部分也会在运行时中报错指出。
值得注意的是,我们在跨平台转换中做的工作不仅是对可转换的技术标准进行转换映射,对于一些目标平台中不存在的能力,我们也尽可能地通过编译和运行时手段提供了模拟和支持,最大限度地减少用户在跨平台开发中需要付出的额外工作量。以差异性最大但现实场景也最多的微信转支付宝为例,Mpx模拟提供了许多微信中支持但支付宝中未支持的能力:
- 组件自定义事件
- 组件间关系
- 获取子组件实例
- observers/property observer
- 内联wxs
- 外部样式类
对于原生自定义组件的跨平台转换,我们会对其进行简单的运行时注入,使其能够使用Mpx框架提供的运行时转换能力。
条件编译
对于框架无法抹平的差异部分,会在编译和运行时报错指出,对于这部分错误,我们提供了完善的条件编译机制让用户能够自行编写目标平台的patch进行修复,该能力也能用于实现具有平台差异性的业务逻辑。
上文中提到Mpx通过读取用户传入的mode和srcMode来决定是否以及如何对项目进行转换,mode和srcMode分别代表整个项目构建的目标平台和源平台,条件编译能够让用户在项目中创建声明了自身平台属性(localSrcMode)的文件和代码块。在项目构建中,框架会优先加载带有localSrcMode声明且localSrcMode与项目目标平台匹配(localSrcMode===mode)的文件和代码块,这部分文件和代码块需要完全依照自身声明的平台标准进行编写,Mpx不会对其进行任何编译和运行时的跨平台转换。
Mpx提供了三种维度的条件编译,分别是文件维度,区块维度和代码维度,用户可以根据平台差异的覆盖范围灵活选择使用。
性能优化
Mpx框架专注于小程序开发,在性能优化方面我们做过很多尝试和努力,主要集中在两个方面:
- 运行时的setData优化
- 编译构建时的包体积优化
setData优化
数据响应是Mpx运行时增强的核心能力,该能力让用户在小程序开发中能够像Vue中一样使用watch和computed特性,并且用直接赋值的方式操作数据驱动视图更新,而不需要手动调用setData方法,换言之框架接管了小程序中的setData调用。
通过各大小程序平台的设计原理和性能优化建议可以得知,setData对于小程序的性能表现非常重要,而setData优化的两大方向在于:
- 尽可能减少setData调用的频次
- 尽可能减少单次setData传输的数据
为了实现setData的优化,我们在模板编译过程中对于每个组件的模板都生成了一个渲染函数(render function),该函数模拟模板的渲染逻辑,在每次执行时访问当次渲染所需的数据,并将当次访问过的数据路径记录下来作为函数返回值返回。
在运行时,框架会在每个组件创建时创建一个render watcher,该watcher追踪渲染函数,当渲染依赖数据发生变更时异步执行渲染函数,在render watcher回调中得到渲染函数返回的数据路径,基于这些路径与上一次的缓存数据进行diff比对,过滤掉未发生变化的数据后得到最小必要数据,最后调用setData将最小必要数据发送到真实的小程序渲染层更新视图。
基于这个机制,当数据发生变更时,只有当前渲染依赖的那部分数据发生变更才会异步地触发render watcher的执行,而每次执行后也只有实际发生变更的那部分数据会被setData发送到渲染层。这样用户就能自由地根据业务需求来操作数据,无需关注setData的调用优化,框架能够自动进行程序上最优的setData调用,在提升用户开发体验的同时也提升了程序性能。
在1.x版本中,渲染函数内无法执行wxs的逻辑,对于含有wxs的组件有可能降级到全量设置数据的模式,在2.0版本中,我们将wxs模块转译处理为js可执行的代码后注入到js bundle中,含有wxs的渲染函数也能够正常访问并执行wxs逻辑。
setData优化示意图
包体积优化
类似于运行时对于setData的接管,Mpx在编译阶段接管了项目的资源管理。得益于webpack强大的插件机制,Mpx开发了一个深度定制的webpack插件,基于webpack完成小程序的打包构建工作。用户在使用Mpx开发小程序时可以不受限制地使用npm依赖、最新的es特性和css预处理器等现代web开发能力。与此同时,Mpx在包体积优化上也做了很多工作,让用户专注于业务开发而无需花费过多精力进行包体积管理,我们所做的优化工作如下:
- 打包构建工作完全基于依赖分析,任何没有被引用的资源都不会出现在dist当中;
- 对于npm组件和页面的构建也完全基于依赖分析按需打包,不会copy整个miniprogram_dist目录,也不需要执行构建npm,使用体验和包体积均优于微信小程序自身的npm支持方案;
- 基于webpack提供的能力进行公共模块抽取和代码压缩等优化工作;
- 完善的分包支持,对所有资源进行从属分析,将所有分包only的资源都输出到分包目录中。
分包作为微信小程序中优化包体积的核心手段(类似于异步按需加载),Mpx对其进行了完善的支持。为了精确地标记出分包only的资源,我们在构建时将主包和分包的依赖收集步骤拆分开来串行处理,先处理主包,再处理分包。在主包的处理过程中,将主包页面中引用的所有非js资源(组件、外部样式、外部模板、wxs,图像媒体等)都记录下来,在处理分包时,对分包内引用的非js资源都进行检查,如果被主包引用过则输出到主包中,否则标记为分包only的资源输出到分包目录下。
对于js模块资源,我们在脚手架中生成的构建配置中提供了辅助函数,便于用户进行分包bundle的配置,经过该配置后,分包only的公用模块会被打入分包bundle输出到分包目录下,其余的公共模块会正常打入主bundle中。
在跨平台开发中,我们建议用户使用Mpx提供的packages来定义分包,这样在转换到不支持分包的小程序平台时会自动降级为同步包进行处理。
分包构建示意图
渐进迁移
Mpx提供了良好的渐进迁移支持,对于使用原生或其他小程序框架的开发者来说,采用渐进迁移的方式逐步引入Mpx进行开发成本并不大。
在2.0版本中我们进一步完善了Mpx的原生兼容性,跟进支持了各个小程序平台最新的技术能力,如自定义tabbar,独立分包,分包预加载,workers,云开发等能力,同时补齐了一些1.x版本遗漏的支持。得益于此,对于使用原生小程序开发的开发者来说,迁移Mpx的成本几乎为0,用户只需将对应页面组件的构造函数替换为Mpx提供的createPage/createCompnent,即可使用Mpx提供的各种增强能力。
对于使用其他框架的开发者,Mpx也提供了局部构建的机制,允许用户将特定的页面和组件单独构建输出为原生组件,用户只需手动或者编写脚本输出的原生组件整合进原有项目中即可。
未来规划
作为滴滴公司内部小程序生态的基础设施,我们会对Mpx框架进行长期的维护更新,确保能在第一时间支持各个小程序平台最新的技术特性。与此同时,我们也会进一步完善框架的基础能力,目前已排上日程待支持能力包括:
- i18n
- ts支持
- 单元测试支持
在跨平台能力方面,我们也会根据社区的反馈和建议,以及小程序的标准化进程,对其进行持续的完善与更新。
最后,如果你专注小程序开发,关注开发体验和产品性能,那Mpx会是你最好的选择。