京东技术:使用JDReact小程序双向转换

软件老王 2018-07-12 14:32:43 ⋅ 634 阅读

来这里找志同道合的小伙伴!


作 者 简 介

严康

京东商城前台产品研发部资深前端工程师,负责JDReact框架前端及小程序转换引擎开发

臧国东

京东商城前台产品研发部前端工程师,负责JDReact框架前端及小程序转换引擎开发


>>>>

概述


JDReact是京东商城前台产品研发部推出的多端融合开发框架。经过不断的技术完善,目前已经在手机京东客户端累计接入100+业务,稳定支撑千万级DAU,并对外支持15+个独立APP,拥有完善的API和功能强大的开发IDE工具。


本文重点介绍了JDReact提供的小程序双向转换工具的原理及用法,通过此工具可以把已经开发的微信小程序低成本转换成JDReact应用,也支持把现有JDReact业务低成本转换成微信小程序应用,完全实现了JDReact和微信小程序生态的打通。

>>>>

背景


此项目的最初灵感来源于我们团队今年5月份参加京东第六届黑客马拉松大赛并获得冠军的项目“微信小程序一键转换工具” 。


因为我们在进行项目开发时,常常会遇到以下情况:


01
场景一:已经开发微信小程序,迁移到APP


项目之初,为了更好的利用微信的流量,更加方便的推广,我们会先直接发布一个小程序,等到后来我们的用户越来越多,应用也变得越来越复杂。这个时候想要把这个应用独立出来,把客户掌握在自己手里,进一步定制应用。此时,没有其他办法,我们只能叫上Android,IOS开发人员,叫上之前的产品经理,之前的测试把之前小程序的功能再重新在原生上实现一遍。  


02
场景二:已经开发APP,迁移至小程序生态


也可能,我们现在已经有了独立App,现在由于种种原因(流量的需求,运营的需要),需要发布一个小程序的版本。怎么办呢?同样我们只能叫上小程序开发人员,之前的产品经理,之前的测试在复制一个小程序的版本。

03
场景三:新业务开发,技术选型中


或者,我们现在即将开始一个新的项目,这个项目既有独立App也有小程序版本(或者可见的未来会有两个版本)。 那么我们是不是需要保持原生团队, 小程序团队,从而进行两个版本的开发呢?

如果我们可以把JDReact的应用转化为小程序,把小程序转化为JDReact应用,那么我们就可以低成本的把原来的JDReact项目/小程序项目移植到另一端了。而且新开始的项目我们就可以根据人员配置只开发一个JDReact的版本或者小程序的版本,等未来需要的时候,直接转化为对应的另一端。

由于只需要维护一端的版本,就可以大大的降低软件工程师的工作,同时产品,测试的工作量也会相应的减轻很多。另外, 我们希望转化之后的代码具有良好的可读性, 方便再次开发与修改。


>>>>

效果演示


我们先用一个实际的例子来展示下转化工具的效果, 我们利用JDReact转换工具将 “值得买京东优选”的微信小程序转化为对应的JDReact版本并运行在手机京东客户端中


首先看一下微信小程序版“值得买京东优选”


转化引擎将会遍历寻找小程序源代码目录中的wxml,进行转化期间会合并其对应的wxss, json, js文件。


转化完成之后,启动生成的JDReact原代码目录,运行模拟器查看效果。


以下是运行中手机京东客户端中的JDReact版“值得买京东优选”



>>>>

原理介绍


不管是React应用还是小程序应用都可以表达为:ui = f(data)。并且他们提供很相似的数据更新方式,小程序是setData(newData, cb), React是 setState(newState,cb),这两个基本条件是我们转化引擎的前提,基于此前提,转化工作理论上是可行的。


f在React里面可以简单的理解为JSX,在小程序里面可以理解为wxml。wxml是小程序提供的“静态”的书写ui的方式灵活性比较低。JSX是react提供的方式,很灵活,里面可以嵌入任何表达式,本质上就是JS。如果我们可以把JSX代码翻译为等效的wxml代码,把wxml代码翻译为等效的JSX代码,那么我们就有能力实现两种应用的转化。

显然,我们的引擎必须能够“读懂”代码,为了实现这个目标,首先我们将代码转化为AST格式,然后根据相应规则不断的修改AST结构,最后生成新的代码。通过babel-parse(把源代码解析为AST),babel-traverse(遍历操作AST),babel-generator(生成新代码)来实现对源代码的操作。这个相应规则源自于两端的等效写法。 比如说: wx:for 和 Array.map的对应, wx:if和逻辑表达式的对应。

然而,并不是所有的规则都这么显而易见


比如JSX

 
  1. getView() {

  2.    return <View/>

  3. }

  4. ...

  5. <View>

  6.    {this.getView()}

  7. </View>

对于这种情况我们会不断遍历JSX表达式,如果发现是函数调用,将会用“返回值替换”, 也就是会用getView的返回值来替换对应JSX表达式,替换的时候需要处理好数据绑定。

再比如下面的JSX

 
  1. render() {

  2.   const a = this.state

  3.   const b = this.f(a)

  4.   return (

  5.         <View>

  6.            {this.h(b) && <View/>}

  7.         <View>

  8.   )

  9. }

wxml的变量绑定“{{}}”是不能出现函数调用(wxs除外)的,这种情况,我们将会使用“表达式前置”, 也就是相应表达式的值提前放置在小程序的data中,转化之后的wxml可能如下:

 
  1. Page({

  2.    data: {

  3.        a: ,

  4.        var1: h(f(a))

  5.    }

  6.    ...

  7. })

  8. /// wxml

  9. <view>

  10.   <view wx:if="{{var1}}"  />

  11. </view>

遍历AST的时候,需要不断的判断JSX表达式是否需要前置。最后转化之后的data会保护很多这样的var。

由于JSX的足够灵活,在进行JSX转向wxml,我们将会有很多类似的转化规则。


那是不是 wxml转JSX就一帆风顺呢? 也不是, 首先一个问题。babel-parse并不识别wxml代码格式。对于wxml,我们需要预处理wxml, 使其可以被parse识别。另外wxml的很多奇怪表现也是我们转化的时候需要兼容的。

比如:

 
  1. Page({

  2.   data: {} // 空对象

  3. })

  4. /// wxml

  5. <view wx:if="{{a.b.c.d}}">hi</view>

小程序的data里面并没有a属性,更别说b属性,c属性。但是这个在小程序里面是表现正常的,而且很常见。我们不希望转化之后的程序在这种情况下报错,我们对这种表达式进行了容错,react-native(预计0.56版本)支持optional-chaining之后,我们也会跟进用optional-chaining来改造这种情况。

wxml到JSX的转化,我们已经基本完成。JSX到wxml的转化已经覆盖所有常见的写法。 随着越来越多的规则被添加进来,我们转化引擎能够覆盖的情况将会越来越多。


对齐两端组件


wxml与JSX的双向转化成功,是转化引擎的第一步。


但是转化引擎应用于实际项目还有一段距离,因为不管是小程序项目还是JDReact项目都不可能只有View, Text组件, 即使我们把users && <FlatList/> 转化为小程序 <FlatList wx:if="{{users}}"/>也是没有作用的,小程序根本就不认识FlatList。 要想让小程序认识FlatList,我们需要在小程序端实现一个小程序版的FlatList,好在发展到今天,小程序的自定义组件已经很完善。


意味着我们需要对齐两端组件,需要在小程序端实现一套JDReact的组件库,包括FlatList, SectionList,JDImage,JDSwiper等,同时实现组件的对应属性。 在React Native端,我们也必不可少的需要实现一套这样的小程序组件,包括 form,radio, radio-groupd等。实际上出于对齐属性的考虑,包括view/View, text/Text这些基本组件,也是通过在另外一端实现对应组件这种方式实现的。


对齐小程序组件库:

对齐React Native 和 JDReact组件库:


生命周期和事件


data驱动视图, 生命周期和事件提供了对data修改的时机。小程序的组件提供了与React相似的生命周期。


小程序自定义组件生命周期:


React的生命周期:

对于两端意义相同的生命周期,比如ready和componentDidMount,会在遍历AST的时候进行修改,对于那些React存在,小程序不存在的生命周期我们会在小程序调用setData前后进行模拟。


另外,小程序的Page具有和组件不一样的生命周期,其中有些比如 onShow,onHide需要和导航器配合实现。


小程序的事件系统源自于web,而RN是自己有一套独立的手势系统,这两种有一定差异。 明显的,小程序的每一个组件都可以响应事件,而RN的组件一般只是Touchable** 系列的组件响应事件。


对于这种情况,我们会检测每一个小程序组件,一旦发现组件响应了事件,就给对应的RN组件加上手势系统, 另外一个比较大的差异,RN的事件是不冒泡的。 除了这些差异, 两边的事件基本是可以对应上的,比如bindtap对应onPress。处理方式和生命周期大同小异。


样式


如果说React Native转化为小程序难点是要处理JSX的灵活,那么小程序项目转化为React Native的坑就是样式了。小程序的wxss源自于css,基本上是css的全集。而React Native采用Yoga作为样式布局系统,Yoga是基于C实现的一套Flexbox布局系统。


所以,在进行小程序样式转化时,原有的小程序wxss代码必须进行适配才可以接入到RN项目中,产生效果,适配过程主要需要解决下面几个问题。

1. RN不支持CSS选择器

React Native中为一个元素指定某种样式,只可采用如下方式:

 
  1. <View style={styles.a}/>


  2. const styles=StyleSheet.creatSheet({

  3.    a:{

  4.        color:'red'

  5.    }

  6. })

在React Native中,只可以通过为某元素明确style来赋予样式,在小程序以及web中,样式赋予则非常的灵活,作为一个简单的例子,

 
  1. <div id='test' class='a'/>

此时只需要在对应的css文件中写

 
  1. div.a{

  2.    color:red

  3. }

在这个例子中,我们用到了css提供的元素选择器(div),类选择器(.a)。css提供了数十种选择器,功能十分强大。然而RN中却没有支持任何一种选择器,因此在进行小程序样式转化前,首先要考虑如何适配小程序的css的选择器功能。

css提供了数十种选择器,且各类选择器间的组合非常灵活,而究其根本,其最基本元素仅有五种:

其余类似于后代选择器之类则可以看作连接符,例如对

 
  1. div .a{

  2.    color=red

  3. }

因此大多数的CSS组合可以看作 [基本元素,连接符,基本元素…] 的形式,考虑到这一点后,我们进一步研究发现,其实所有基本类型选择器都可以由某个标签的标签名,以及prop属性来获取,而所有连接符关系,都可以通过元素在小程序wxml文件中的文档结构来进行计算匹配,我们通过抽象语法树的方式解析wxml文件,为每个元素注入了它自身在文档结构中的信息,来进行选择器的计算适配,目前已经提供了近10种最常用的选择器类型,且功能在不断的完善与扩展。

2. CSS写法的不一致

RN与小程序对于CSS中的写法差异较大。选择器方面,小程序CSS中选择器名可以为相对随意的字符串,例如’test-a¥b’也是有效的选择器名,而在RN中,这并不是一个有效的变量命名,因此我们在RN中,我们将所有的选择器名定位字符串类型,例如上述选择器名将转为

 
  1. "test-a¥b":{}

因此可以完整适配小程序中任意的命名方式。


另一方面,在属性上存在写法不一致的情形。例如,小程序中写为border-width,而适配到RN中,则需要转化为borderWidth,不仅如此,对于一些简写的属性,例如小程序CSS中的

 
  1. div {margin 10px 0;}

有着明确的语意,然而在RN中,无法解析这样的语法,我们也对此进行了转化,例如对于上述情形,我们在RN中解析并转化为了

 
  1. "div":{

  2.    marginTop : 10px;

  3.    marginBottom : 10px;

  4.    marginLeft : 0;

  5.    marginRight : 0;

  6. }

在RN中与小程序还有众多写法不一致的情形,对此我们尽最大可能提供了支持,并给出了规范。

3. 在RN与CSS中存在属性默认值的不同

RN与小程序CSS存在很多属性默认值的不同,这就导致了,即使选择器适配功能完好,同样的CSS代码,在小程序上表现正常,RN上则显示不正确。


比如,RN中采用flex布局,其flex方向默认为列布局,而在小程序CSS则默认为行布局。又如,RN中的flexShrink默认值为0,小程序CSS中则为1,这会导致页面展示的不正常。


对此,我们提供了适配方案,首先我们会对小程序开发者提出一些基本要求,例如必须采用flex布局方式。另一方面,我们会对于每个RN中与小程序CSS中默认值存在差异的情况进行修正,尽可能让小程序开发者不改变自己的CSS写法。对于上述两种情形,我们都会提供出具体的规范。


我们仔细研究了小程序CSS与RN中CSS的不同,并在最大程度上适配了小程序CSS的写法,让用户可以自由使用小程序CSS的各项功能,这一切都是为了让开发者获得更好的开发体验。


另外,为了提供更好的服务,我们制定了具体的规范,确保小程序开发者在现有规范下开发完成后,转化前与转化后页面展示完全一致。
css转化流程总结如下:


限制与约束


有的时候, 知道我们“做不到什么”更加重要。


由于AST只是静态分析代码,许多“运行时”才能够得到的信息是得不到的,比如:

 
  1. getView() {

  2.   let a = null

  3.   ...

  4.   ...

  5.   return a

  6. }

  7. <View>

  8.    {this.getView()}

  9. </View>

这种情况,我们根本不知道a 到底是什么, “返回值替换” 就会出问题。


又比如:

 
  1. import React, { Comonent } from 'react'

  2. const ForFun = Component

  3. class X extends ForFun {

  4. }

这里的ForFun会直接导致我们判断React组件失败,代码需要自律!

React中的高价组件暂时不支持转换,并且我们目前只支持React Native官方组件和JDReact通过的组件。

两边系统的差异和限制,在小程序端,比如小程序的包大小要在2M以内, 那么当JDReact转化过来的小程序打完包也必须在2M以内, 比如小程序的tab页个数,路由深度也是有限制的,

另外,前文提到的,在小程序向React应用转化的时候,对小程序本身所使用的样式是有限制的。

在RN端,小程序的scroll-view是可以上下左右滚动的,而RN的只可以一个方向, 事件处理的差异等等。

对于所有的这些限制和约束,我们后续会给出一份完整的清单,同时也会给出相应的替换方案。


>>>>

合作共赢


目前我们已经初步实现了反向转换引擎,正向转换引擎也正在加速开发中。非常欢迎有兴趣的个人或团队和我们交流合作,合作试用可以在公众号下方留下您的联系方式!!!


---------------END----------------

后续的内容同样精彩

长按关注“IT实战联盟”哦




全部评论: 0

    我有话说:

    京东到家订单中心系统mysql到es的转化之路

    原文:https://www.toutiao.com/i6796507988602389006 京东到家订单中心系统业务中,无论是外部商家的订单生产,或是内部上下游系统的依赖,订单查询的调用量都非常

    微信程序 - iconfont 图标字体

    你还在使用图片作为程序的图标?大猪告诉大家如何在程序使用iconfont字体图标

    微信程序营销之大转盘(二)

    第一个版本的大转盘都是用图片做的,奖品等信息都是不无法修改的,说白了就是没啥实际用途,作者我就直接用canvas撸了一个全手工绘制的大转盘分享给大家

    微信程序-Image的widthFix属性和rpm尺寸的使用

    在做微信程序的商品详情页,商品的详情是图片集合,渲染完成后发现图片加载的很不自然

    微信程序实现商品数量加减

    这是一个用微信程序原生代码实现的数量加减demo,主要是用于商品购物车或者商品详情修改数量使用

    微信程序抖音实战-首页(下)

    抖音程序首页动态数据获取

    微信程序电商实战-入门篇

    程序开发工具有新版本更新啦!开发体验更好了,接下来一起为电商程序做一下准备前期准备工作~~

    微信程序电商实战-商品详情(上)

    先看一下今天要实现的程序商品详情页吧!

    微信程序抖音实战-首页(上)

    你也可以用微信程序编写一个抖音

    「开源资讯」BookChat v2.4 发布,通用书籍阅读微信程序

    BookChat - 面向程序员的开源书籍和文档阅读学习程序

    微信程序实战篇:程序之页面数据传递

    我们在写程序的时候经常会遇到子页面向主页面回传数据或者普通页面跳转到tabBar 页面携带数......

    微信程序:最新微信登录授权并获取openid等信息

    为优化用户体验,使用 wx.getUserInfo 接口直接弹出授权框的开发方式将逐步不再支持。从2018年4月30日开始,程序游戏的体验版、开发版调用 wx.getUserInfo 接口,将

    程序实战-幸运大转盘

    微信程序幸运大转盘

    微信程序电商实战-购物车(上)

    好久没更新程序电商实战的文章了,是因为最近一直做整体架构调整,一些准备工作也是比较耗费时间的。在这几天将会有新版的 程序电商教程推出.......

    微信程序电商实战-首页(下)

    上一篇:微信程序电商实战-首页(上)好了,上一期我们把首页搜索、导航栏和广告轮播给做完了,那么接下来会继续

    微信程序实战篇:基于wxcharts.js绘制移动报表

    微信程序图表插件(wx-charts)是基于canvas绘制,体积巧,支持图表类型饼图、线图、柱状图 ......

    微信程序电商实战-首页(上)

    上一篇:微信程序电商实战-入门篇 嗨,大家好!经过近两周的精心准备终于开始微信程序电商实战之路喽。那么最终会做成什么样呢?好了,不啰嗦了 我们先看首页长什么样吧!   首页效果图