跨平台编译

自2.0版本开始,mpx开始支持跨小程序平台编译,不同于常规跨平台框架重新定义一套DSL的方式,mpx支持基于现有平台的源码编译为其他已支持平台的目标代码。跨平台编译能力依赖于mpx的多平台支持,目前mpx已经支持将微信小程序跨平台编译为支付宝、百度、qq和头条小程序。

使用方法

如果你是使用mpx init xxx新生成的项目,package.json里script部分有npm run build:cross,直接执行npm run build:cross(watch同理),如果仅需构建某几个平台的,可以修改该script,按已有的格式删除或增添某些某些平台

如果你是自行搭建的mpx项目,你只需要进行简单的配置修改,打开项目的webpack配置,找到@mpxjs/webpack-plugin的声明位置,传入mode和srcMode参数即可,示例如下

  1. // 下面的示例配置能够将mpx微信小程序源码编译为支付宝小程序
  2. new MpxwebpackPlugin({
  3. // mode为mpx编译的目标平台,可选值有(wx|ali|swan|qq|tt)
  4. mode: 'ali',
  5. // srcMode为mpx编译的源码平台,目前仅支持wx
  6. srcMode: 'wx'
  7. })

跨平台差异抹平

为了实现小程序的跨平台编译,我们在编译和运行时做了很多工作以抹平小程序开发中各个方面的跨平台差异

模板语法差异抹平

对于通用指令/事件处理的差异,mpx提供了统一的编译转换抹平操作;而对于平台组件和组件属性的差异,我们也在力所能及的范围内进行了转换抹平,对于平台差异性过大无法转换的部分会在编译阶段报错指出。

组件/页面对象差异抹平

不同平台间组件/页面对象的差异主要体现在生命周期上,我们在支持多平台能力时已经将不同平台的生命周期映射到mpx框架的一套内部生命周期中,基于这个统一的映射,不同平台的生命周期差异也得到了抹平。

此外,我们还进行了一系列运行时增强来模拟微信平台中提供而其他平台中未提供的能力,例如:

  • 在支付宝组件实例中提供了this.triggerEvent方法模拟微信中的自定义组件事件;
  • 提供了this.selectComponent/this.selectAllComponents方法模拟微信中获取子组件实例的能力;
  • 重写了createSelectorQuery方法抹平了微信/支付宝平台间的使用差异;
  • 转换抹平了微信/支付宝中properties定义的差异;
  • 利用mpx本身的数据响应能力模拟了微信中的observers/property observer能力等;
  • 提供了this.getRelationNodes方法并支持了微信中组件间关系relations的能力

对于原生小程序组件的转换,还会进行一些额外的抹平,已兼容一些已有的原生组件库,例如:

  • 将支付宝组件中的props数据挂载到this.data中以模拟微信平台中的表现;
  • 利用mpx本身的mixins能力模拟微信中的behaviors能力。

对于一些无法进行模拟的跨平台差异,会在运行时进行检测并报错指出,例如微信转支付宝时使用moved生命周期等。

json配置差异抹平

类似于模板语法,会在编译阶段进行转换抹平,无法转换的部分会在编译阶段报错指出。

api调用差异抹平

对于api调用,mpx提供了一个api调用代理插件来抹平跨平台api调用的差异,使用时需要在项目中安装使用@mpxjs/api-proxy,并且在调用小程序api时统一使用mpx对象进行调用,示例如下:

  1. // 请在app.mpx中安装mpx插件
  2. import mpx, { createApp } from '@mpxjs/core'
  3. import apiProxy from '@mpxjs/api-proxy'
  4. mpx.use(apiProxy, {
  5. // 开启api promisify
  6. usePromise: true
  7. })
  8. createApp({
  9. onLaunch() {
  10. // 调用小程序api时使用mpx.xxx,而不要使用wx.xxx或者my.xxx
  11. mpx.request({url: 'xxx'})
  12. }
  13. })

对于无法转换抹平的api调用会在运行时阶段报错指出。

webview bridge差异抹平

对于不同平台中webview bridge的调用,我们封装了一个@mpxjs/webview-bridge包,用于抹平不同小程序平台中webview bridge的差异,简单使用示例如下:

  1. import mpx from '@mpxjs/webview-bridge'
  2. mpx.navigateBack()

对于无法转换抹平的bridge调用会在运行时阶段报错指出,详细使用指南请查看webview-bridge

跨平台条件编译

Mpx跨平台编译的原则在于,能转则转,转不了则报错提示,对于无法抹平差异的部分,我们提供了完善的跨平台条件编译机制便于用户处理因平台差异而无法相互转换的部分,也能够用于实现具有平台差异性的业务逻辑。

mpx中我们支持了三种维度的条件编译,分别是文件维度,区块维度和代码维度,其中,文件维度和区块维度主要用于处理一些大块的平台差异性逻辑,而代码维度主要用于处理一些局部简单的平台差异。

文件维度条件编译

文件维度条件编译简单的来说就是文件为维度进行跨平台差异代码的编写,例如在微信->支付宝的项目中存在一个业务地图组件map.mpx,由于微信和支付宝中的原生地图组件标准差异非常大,无法通过框架转译方式直接进行跨平台输出,这时你可以在相同的位置新建一个map.ali.mpx,在其中使用支付宝的技术标准进行开发,编译系统会根据当前编译的mode来加载对应模块,当mode为ali时,会优先加载map.ali.mpx,反之则会加载map.mpx。

文件维度条件编译能够与webpack alias结合使用,对于npm包的文件我们并不方便在原本的文件位置创建.ali的条件编译文件,但我们可以通过webpack alias在相同位置创建一个虚拟的.ali文件,并将其指向项目中的其他文件位置。

  1. // 对于npm包中的文件依赖
  2. import npmModule from 'somePackage/lib/index'
  3. // 配置以下alias后,当mode为ali时,会优先加载项目目录中定义的projectRoot/somePackage/lib/index文件
  4. const webpackConf = {
  5. resolve: {
  6. alias: {
  7. 'somePackage/lib/index.ali': 'projectRoot/somePackage/lib/index'
  8. }
  9. }
  10. }

区块维度条件编译

在.mpx单文件中一般存在template、js、stlye、json四个区块,mpx的编译系统支持以区块为维度进行条件编译,只需在区块标签中添加mode属性定义该区块的目标平台即可,示例如下:

  1. <!--编译mode为ali时使用如下区块-->
  2. <template mode="ali">
  3. <!--该区块中的所有代码需采用支付宝的技术标准进行编写-->
  4. <view>支付宝环境</view>
  5. </template>
  6. <!--其他编译mode时使用如下区块-->
  7. <template>
  8. <view>其他环境</view>
  9. </template>

代码维度条件编译

如果只有局部的代码存在跨平台差异,mpx同样支持在代码内使用if/else进行局部条件编译,用户可以在js代码和template插值中访问__mpx_mode__获取当前编译mode,进行平台差异逻辑编写,js代码中使用示例如下。

除了 __mpx_mode__ 这个默认插值以外,有别的环境变量需要的话可以在mpx.plugin.conf.js里通过defs进行配置。

  1. if(__mpx_mode__ === 'ali') {
  2. // 执行支付宝环境相关逻辑
  3. } else {
  4. // 执行其他环境相关逻辑
  5. }

template代码中使用示例如下

  1. <!--此处的__mpx_mode__不需要在组件中声明数据,编译时会基于当前编译mode进行替换-->
  2. <view wx:if="{{__mpx_mode__ === 'ali'}}">支付宝环境</view>
  3. <view wx:else>其他环境</view>

JSON中的条件编译(注意,这个依赖JSON的动态方案,得通过name=”json”这种方式来编写,其实写的是js代码,最终module.exports导出一个可json化的对象即可):

  1. <script name="json">
  2. const pages = __mpx_mode__ === 'wx' ? [
  3. 'main/xxx',
  4. 'sub/xxx'
  5. ] : [
  6. 'test/xxx'
  7. ] // 可以为不同环境动态书写配置
  8. module.exports = {
  9. usingComponents: {
  10. aComponents: '../xxxxx' // 可以打注释 xxx组件
  11. }
  12. }
  13. </script>

样式的条件编译:

  1. /*
  2. @mpx-if (
  3. __mpx_mode__ === 'wx' ||
  4. __mpx_mode__ === 'qq'
  5. )
  6. */
  7. /* @mpx-if (__mpx_mode__ === 'wx') */
  8. wx {
  9. background: green;
  10. }
  11. /*
  12. @mpx-elif (__mpx_mode__ === 'qq')
  13. */
  14. qq {
  15. background: black;
  16. }
  17. /* @mpx-endif */
  18. /* @mpx-if (__mpx_mode__ === 'swan') */
  19. swan {
  20. background: cyan;
  21. }
  22. /* @mpx-endif */
  23. always {
  24. background: white;
  25. }
  26. /*
  27. @mpx-else
  28. */
  29. other {
  30. /* @mpx-if (__mpx_mode__ === 'swan') */
  31. background: blue;
  32. /* @mpx-else */
  33. background: red;
  34. /* @mpx-endif */
  35. }
  36. /*
  37. @mpx-endif
  38. */

其他注意事项

  • 当目标平台为支付宝时,需要启用支付宝最新的component2编译才能保障框架正常工作,关于component2点此查看详情
  • 跨平台源码中自定义组件的标签名不能使用驼峰形式myComponent,请使用横杠形式my-component来书写;
  • 生成的目标代码中文件名和文件夹名不能带有@符号,目前媒体文件和原生自定义组件在编译时不会修改文件名,需要重点关注。