1
本文首发于 vivo互联网技术 微信公众号 
链接:[](https://mp.weixin.qq.com/s/Cw... https://mp.weixin.qq.com/s/oGX4XSm8F4fa1ocLdpyqlA
作者:悟空中台研发团队

【悟空活动中台】系列往期精彩文章:

一、背景

随着小程序、快应用的用户体验越来越友好,用户群体庞大,运营小伙伴开始偏向将营销活动投放至微信、支付宝、快应用等微应用中。

小程序和快应用可以比作更加“轻便”的应用,与传统应用相比优点是:体积小、加载快、无需安装、精准触达等。因此众多企业都迫切希望在微应用的蓝海里抢占先机,获取海量的渠道流量和优秀营销效果。

对于活动研发而言,各端小程序底层实现不一致,技术生态隔离,研发过程需要适配各平台技术上差异。如果单纯采用 case by case 的开发模式,学习成本高、适配周期长、技术风险点多,易产生过高的人力成本,难以满足运营快速搭建多样化活动的诉求。

基于上述痛点,我们想借助悟空中台的能力打通技术壁垒,实现小程序插拔式、可视化 、自适配各小程序平台和快应用。带着这个初心,开启对悟空活动中台多端改造之旅。

二、多端场景技术挑战

探索插拔式多端配置平台,我们首先梳理下技术难点:

1、企业级能力复用

悟空已经提供给运营和活动开发丰富的配套工具和解决方案,多端活动场景想要对运营和开发友好,必须在悟空能力上去扩充,毕竟传统H5活动碰到的问题,多端活动场景也会遇到,我们可以站在巨人的肩膀上探索。

复用悟空活动中台最大的挑战是遵循workless工作流中的微前端架构方案,基于该方案的特点去扩展(组件热插拔,子系统独立部署)。

多端架构核心是利用平台化手段沉淀复用已有技术能力,实现多端微组件热插拔,一方面平台能够适配多端组件,另一方面多端组件的开发模式脱离于平台 ,自成服务,这就需要探索是否有框架能够适应平台化改造,实现高内聚,低耦合。

2、动态构建目标活动

目前,各个小程序的技术各不相同,举个例子,我们罗列下不同小程序和快应用核心 api 的使用方式。

由上表可以比对出不同的小程序,快应用语法和 api 都不相同。这就意味着,同一个功能,开发者要与不同端一一对接,几何倍的增加了开发的成本。

多端活动配置,基于提升配置效率的目的,必须要考虑实现自动化编译不同目标程序,这就需要多端组件服务化,构建服务与组件服务解耦,根据配置动态拉去服务组件,远程动态构建。

3、快应用的深度支撑

本次多端探索的重点是对快应用的快速支撑。快应用是由 11 家手机厂商联合推出,投入流量超 10 亿,同时在多家手机终端曝光导流。vivo 提供给快应用海量入口级资源曝光,包括负一屏、智慧场景、应用商店、浏览器等。

快应用的优势很明显,如何利用快应用技术特点和核心能力,将传统活动转换成快应用活动是这次探索的重点,我们希望能寻得连接悟空和快应用的桥梁。

三、寻找复用平台能力最优解:多端框架

1、技术探索

悟空活动中台前端技术选型为vue,我们需要对vue语法做DSL,来适配多端。基于该技术条件,我们锁定本次探索关键点:多端框架。

我们先整理下思路:如何设计一个低耦合,高内聚多端框架?

概括梳理后,设计分为三个阶段:

  1. DSL阶段:创建一个 DSL 语法解释器,并确定一个语法对应解释枚举列表。
  2. AST阶段:创建一个 AST 基础语法树,将DSL转换成AST语法树,再对AST进行transform,最后生成代码。这个阶段其实我们都很熟悉,就是babel在编译 ES6/7/8 的代码时在做的事。换句话说,可以借助babel来节省转换成本。
  3. AST转换: 在AST基础语法树的transform规则之外进行扩展,内容主要是小程序和快应用端的转换规则。

下面对这三个阶段详细说明:

1.1、对 vue 的 DSL

多端适用需要避免开发者操作原生DOM结构,因此需要抽象出一些基础组件,来模拟div、ul等标签 。我们在AST的转换过程中需要去处理div,span等基础Web DOM元素,替换为自定义基础组件。

1.2、根据不同小程序语法进行语法转换

1.2.1、AST 是什么

引用维基的描述:

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有两个分支的节点来表示。

大部分程序语言都通过 AST 把代码转换为字节码以便计算机执行,由于JavaScript所处环境的特殊性(依托于浏览器,新语言特性依赖执行环境的支持,在浏览器暴露代码,容易遭受攻击),导致AST在JavaScript中拥有更多用武之地。

举几个栗子:

  • @babel/polyfill的作用是将代码中的ES6语法转化成 ES5 语法。
  • webpack的 UglifyJs 插件常用于代码压缩,混淆,美化等操作。

通过上述例子我们发现AST常用于前端工具中,可以无感知处理底层代码转换。

1.2.2、编译vue文件

我们可以使用 vue-template-complier中的parseComponent 方法剥离 template、script、style ,并利用compile方法将template转化为对应的 AST,最终通过 @babel/parser解析 Javascript 以及css 代码。

1.2.3、AST 转换

AST的操作主要分为三阶段:

  • 将代码转化为AST。
  • 对AST进行深度遍历并转换。
  • 将转换后的AST转回相应代码。

以上步骤,我们可以利用@babel/parser,@babel/traverse,@babel/generator来处理。

多端适配工作,大致分为以下几个模块:

  • 不同平台 css  兼容,并对一些  css 兼容作降级处理。
  • 不同平台 template兼容,比如<span> -> <text>,<div> -> <view>等等,这块原则是基于上层DSL语义的规则并具备各端适用性。
  • 不同平台 Javascript 兼容。在此阶段我们对这些语法糖进行适配处理,比如说window 或是document  等。

上述适配工作完成后,我们需要将不同版本的AST转换成多端适配代码,再借助 loader生成不同端目录结构。

通过对 babel,AST 的梳理,能够发现一个问题:开发多端框架,会产生相应的研发成本,后续框架的维护、扩展需要长期人力投入。

我们真的需要去纯手写一个多端框架么?下面是成熟开源多端框架研究梳理。

1.3、技术选型:各厂多端框架调研

截至到目前,已经有包括但不限于 Google,Facebook,阿里,腾讯,美团,京东,滴滴发布了自己的开源多端通用框架,详细清单如下

简单对比:

从技术层面来说,Flutter属于全包型,从底层引擎到中层DSL,到上层业务框架全部包含在里面,这就从极大程度上保证了一套代码多端渲染时的一致性。

RN和Weex严格来说不属于多端通用框架,不能原生支持小程序端和快应用端。

京东出品的Taro框架是一套遵循 React 语法规范的多端开发解决方案。

最终从业务技术选型出发,我们优先选择了Dcloud团队的uni-app。

1.3.1、uni-app

uni-app在设计思路上遵循通过转义 view和viewModel代码为AST语法树,并将AST转化为各终端匹配的代码,完成多端的适配。

借用官方对该框架的描述:

uni-app 是一个使用 Vue.js 开发小程序、H5、App 的统一前端框架。开发者使用 Vue 语法编写代码,uni-app 框架将其编译到 小程序(微信/支付宝/百度/字节跳动/QQ/钉钉)、App(iOS/Android)、H5 等多个平台,保证其正确运行并达到优秀体验。

uni-app 框架特点如下:

  • 完整的 vue 开发体验。
  • 彻底的组件化开发能力。
  • 完美支持vuex数据管理方案。

上述特点无缝与悟空技术栈对接,同时该框架支持多端发布,可以编译成小程序、H5 等平台代码。

1.3.2、uni-app 对 vue 的改造

uni-app 作为小程序和 vue 的中间层,在编译和运行时对数据同步和事件代理做了改造,保证开发者使用 vue 语法开发就能对接不同端小程序。

数据同步:当组件触发数据变化时,uni-app 修改了 initProperties 初始化属性方法,在数组和对象遍历元素数据,并且创建 observer 监听 value 变化,将数据同步至小程序。

事件代理:uni-app 作为中间层将不同小程序的事件转换为 vue 的事件,并支持大部分 web 事件。

原理上来说多端框架都是通过内嵌式DSL来实现。然后根据各端的差异性,使用一套 DSL 来描述,用多套AST转换规则来支撑。

四、多端微组件动态构建目标活动

1、使用 uni-app 构造多端微组件

下面我们通过步骤演示的方式,来演示通过 uni-app 开发一个微组件,以及平台实现微组件动态渲染。

步骤一:本地 多端 SFC 组件

<template>
  <view>
    <image  :src="item.imgSrc" mode="aspectFill"></image>
  </view>
</template>
<script>
export default {
  props: ['item']
}
</script>

该多端SFC文件开发方式与普通 vue 的 SFC 组件类似,只是语法上遵循 uni 提供的组件和 api。

步骤二:编译成 umd.js

如何将多端 SFC 组件编译成 umd 文件?

cli 脚手架支持采用@vue/cli+@dcloudio/vue-cli-plugin-uniUNI_PLATFORM 的方式,根据参数自定义目标小程序的 umd. js 文件, 并且还可以通过 cli 自定义编译平台:

{
  /**
     package.json其它原有配置
     */
  "uni-app": {
    // 扩展配置
    "scripts": {
      "custom-platform": {
        //自定义编译平台配置,可通过cli方式调用
        "title": "自定义扩展名称", // 在HBuilderX中会显示在 运行/发行 菜单中
        "BROWSER": "", //运行到的目标浏览器,仅当UNI_PLATFORM为H5时有效
        "env": {
          //环境变量
          "UNI_PLATFORM": "" //基准平台
        },
        "define": {
          //自定义条件编译
          "CUSTOM-CONST": true //自定义条件编译常量,建议为大写
        }
      }
    }
  }

uni 脚手架集成 vue-cli-service 构建方式,生成 componet.umd.min.js:

cross-env NODE_ENV=production UNI_PLATFORM=H5 vue-cli-service build --target lib --name code './src/plugin/code.vue'  --dest ./src/plugin/dist  --no-clean

步骤三:线上渲染

如何在动态组件的umd.js中的组件对象导出并在web端使用呢?首先看下多端的 umd.js:

;(function(e, t) {
  'object' === typeof exports && 'object' === typeof module
    ? (module.exports = t())
    : 'function' === typeof define && define.amd
    ? define([], t)
    : 'object' === typeof exports
    ? (exports['code'] = t())
    : (e['code'] = t())
})('undefined' !== typeof self ? self : this, function() {
  return (function(e) {
    var t = {}
    ...
    ...
    ...
  })['default']

代码的形式与普通微组件转换为 umd.js 是一致的,所以 plugin 组件渲染的方式也是通过 vue 自带 component 动态组件来渲染。

<template>
  <div>
    <component :is="mode" v-if="mode"></component>
  </div>
</template>
<script>
export default {
  data() {
    return {
      mode: null
    };
  },
  methods: {
    async load() {
      ...
      //此时内部的self变量,被外部变量mode代替,成功将组件对象导出
      new Function("self", `return ${await data.text()}`)(mode);
      this.mode = mode.code;
      ...
    }
  }
};
</script>

微组件与普通微组件的显著区别如下:

  • 多端微组件 api 与普通组件不一致。
  • 底层能力需要 uni 支撑。
  • 必须通过 mpType 在生命周期里声明渲染多端组件。

多端微组件的渲染特点是同时在一个页面内渲染,需要页面组件 page-index 遍历组件直接渲染到 view 中:

2、多端微组件数据管理

按照平台开发规范,一个标准的微组件结构是这样:

├── code.vue     # 编辑器中渲染的UI组件
├── prop.vue     # 属性面板中渲染的配置组件
├── setting.json # 存储初始化基础配置和业务配置
└── package.json # 依赖信息

在平台服务中,H5 设计器中配置面板prop.vue运行的 H5 环境,而多端组件code.vue运行在页面设计区。

在 H5 编辑器设计上,我们采用独立的沙箱环境,设计区和平台环境相互解耦,多端微组件复用 H5 专题页(组件与组件之间),平台的编辑器环境(组件和平台之间)。

这种设计可以解决如下问题:

  • 解决组件的可配置化
  • 组件环境与平台环境解耦
  • 将专题数据和多端环境进行解耦

微组件的数据是围绕着 item 去变化的,code.vue 只需要遵循 vue 单文件组件的开发规则,以及使用 uni 提供组件去开发,极大的降低了组件的开发门槛,最终 code.vue 平台会使用 uni/cli 直接编译成 UMD 文件,运行至编辑器的沙箱环境中,完全复用组件之间数据传递的规则,具体的运行逻辑图见:

多端微组件完全遵循 vue 语法,无需太多学习成本,如果看懂了上述开发流程以及运行方式,那么恭喜,你已经掌握了多端改造的精髓。

3、多端微组件如何构建目标应用

多端微组件与常规微组件的业务流程是一致的,最终通过不同的组件搭配和数据配置,生成符合运营预期的专题,发布后生成站点。

值得注意的是:微组件通过 node 服务下发,可以自定义初始脚手架,构建命令,多端微组件基于该特点以及普通微组件建 H5 应用的方式,将 uni 脚手架作为站点底层脚手架,动态拉取站点配置的多端微组件,最终根据动态修改 UNI_PLATFORM 构建命令生成目标小程序代码包。

上图解释了整个站点构建流程,可以看到多端服务组件配合远程脚手架可以根据不同的构建命令生成对应平台的应用资源包。

4、一个完整的微组件 demo

我们通过简单的 demo 体验下多端微组件开发模式。

以下展示最高频率使用的图片组件为列,code.vue 单文件组件可以使用 vue 语法直接开发。

<!-- template语法,直接使用image标签 -->
<template>
    <image :style="style" :src="item.imgSrc" mode="aspectFill"></image>
</template>
<script>
export default {
  //item为配置数据,prop数据修改item通过vuex直接同步至code
  props: ['item'],
  computed: {
    style() {
     //根据item数据设置图片大小
      let style = {}
      style.width = this.item.width
      style.height = this.item.width/this.item.vivo_scale
      return style
    }
  }
}
</script>

配置侧 prop.vue 只需要展示图片的链接地址,让运营实时配置、修改图片链接。

prop 配置侧运行在 pc 端,无需使用 uni 语法,遵循传 vue 单文件开发规范。

<template>
  <el-form label-width="70px" :model="item" class="vivo-hot-config">
    <el-form-item label="素材图片">
      <media-picker :defaultSelect="item.imgSrc" @select-change="selectImg"></media-picker>
      <div class="jy-size">建议上传50KB以内图片</div>
    </el-form-item>
  </el-form>
</template>
<script>
import MediaPicker from '@wukong/mediaPicker'
export default {
  components: {
    MediaPicker
  },
  name: '',
  props: ['item'],
  methods: {
    selectImg(img) {
      if (img.mediaPath) {
        this.item.imgSrc = img.mediaPath
        image.onload = () => {
          this.item.vivo_scale = image.width / image.height
        }
      } else {
        this.item.imgUrl = ''
      }
    }
  }
}
</script>
</style>

其中MediaPicker是平台给开发者提供的媒体库内置组件,用来连接开发者与平台,开发者根据内置的 api 就可以获取平台素材库能力。

对于配置数据prop.vue,沿用原有构建逻辑,而 code.vue 则是用 uni 语法开发,最终通过 uni+vue 打包方式,生成 code.umd.min.js。

初始化的配置参数只需要声明图片的配置:长、宽、链接。

{
  "imgSrc": "https://www.baidu.com/img/bd_logo1.png",
  "height": 320,
  "vivo_scale": 1
}

最终展示下集成至平台的简单图片组件:

五、快应用深度支撑

1、快应用基本特征

快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定的,以平台化的生态模式对个人开发者和企业开发者全品类开放。

2、快应用核心能力及优势

为了扶持快应用生态,赋能开发者,拓展场景未来,vivo 启动“快应用百万计划”,面向广大开发者开放申请。亿级流量投入,联合定制化扶持方案, 打造百万级 DAU 行业标杆,共同创建完整健康的快应用生态。

vvivo 对快应用的投入和支持也是非常庞大的:

  • 系统层级添加桌面能力:保证留存
  • 分享,支付能力:支持第三方分享和支付
  • 语音场景支持:支持语音唤醒快应用
  • vivo 账号打通能力:支持一键 vivo 账号授权登 录,可透传手机号
  • URL 跳转能力:任何渠道 CP 的 H5 页面可拉起快应用
  • 添加负一屏卡片能力:快应用内可添加对应卡片至负一屏
  • POI 能力:根据地理位置提示服务

3、快应用深度定制

在此大前提下,悟空提供传统手动开发快应用模式转换为自助快应用建站,以此来扩充 IOT,推广活动,电商场景等玩法。

多端微组件至快应用小程序转换流程:

多端微组件配置化专题后,我们可以选择转换不同的客户端,并且 vivo 提供了小程序转快应用的能力,转换工具本身根据快应用标准,对逻辑层和代码做了深层的定制,保证渲染速度和使用体验。

六、探索成果演示

经过小伙伴们的共同努力以及 uni-app 的技术支撑,关键技术环境已全部打通,内部产品正在有条不紊的落地,静待花开。下面 demo 展示下最终配置效果:

  • 基于悟空编辑器可视化能力,实现多端组件实时预览,可视化配置
  • 多端编译,可视化选择将站点打包成小程序,快应用,支付宝,头条小程序运行
  • 基于 uni-app 的组件开发模式,一套组件,运行多个平台

对于活动的角色而言:

  • 开发者:实现了组件复用,遵循 vue 语法,无需知识迁移
  • 运营:上线多种形态的产品引流,增加投放的渠道
  • 产品:低成本拓展产品矩阵

七、开发多端微组件注意事项

uni-app 为抹平不同小程序端的差异做了很多工作, 多端组件开发过程中也有注意点:

  • 不能直接操作 dom,也不能使用 document、window、localstorage、cookie 等 ,只能通过 uni 提供的 api 完成
  • 原生 web 库无法支持,只能使用 uni 插件市场推荐的第三方库
  • 开发时需尽量规避逻辑层与视图层分离带来的通信损耗
  • 如如果组件有特定客户端的业务逻辑,可以使用 uni 提供的特色条件编译

八、写在最后

通过本文对多端改造的介绍,相信大家已体验多端微组件开发模式,并对多端活动配置平台如何实现插拔式、自适配各小程序有一定的想法。

上述的探索只是悟空多端之路的开始,在丰富组件生态以及提供多端解决方案上,我们还有很长的路要走,后续实践出阶段性成果也会分享给大家,欢迎一起沟通讨论。

更多内容敬请关注 vivo 互联网技术 微信公众号

注:转载文章请先与微信号:Labs2020 联系


vivo互联网技术
3.3k 声望10.2k 粉丝