JDReact小程序双向转换工具介绍

作 者 简 介

严康

京东商城前台产品研发部资深前端工程师,负责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

getView() {
    return <View/>
}

...
<View>
    {this.getView()}
</View>

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

再比如下面的JSX

render() {
   const a = this.state
   const b = this.f(a)

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

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

Page({
    data: {
        a: ,
        var1: h(f(a))
    }
    ...
})

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

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

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

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

比如:

Page({
   data: {} // 空对象
})

/// wxml

<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中为一个元素指定某种样式,只可采用如下方式:

<View style={styles.a}/>

const styles=StyleSheet.creatSheet({
    a:{
        color:'red'
    }
})

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

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

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

div.a{
    color:red
}

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

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

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

div .a{
    color=red
}

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

2. CSS写法的不一致

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

"test-a¥b":{}

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

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

div {margin 10px 0;}

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

"div":{
    marginTop : 10px;
    marginBottom : 10px;
    marginLeft : 0;
    marginRight : 0;
}

在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只是静态分析代码,许多“运行时”才能够得到的信息是得不到的,比如:

getView() {
   let a = null
   ...
   ...
   return a
}

<View>
    {this.getView()}
</View>

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

又比如:

import React, { Comonent } from 'react'

const ForFun = Component
class X extends ForFun {
}

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

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

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

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

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

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

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

原文发布于微信公众号 - 京东技术(jingdongjishu)

原文发表时间:2018-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏BeJavaGod

以最简单的登录为例,诠释JS面向对象的简单实例

JavaScript,是前端开发人员必须会的一门技术,从JS演变出来的有很多框架,先说说几个热门的框架吧: JQuery:这个技术必须会,如果不会,那一定要会查...

3397
来自专栏较真的前端

RxJS福利~~

9425
来自专栏hightopo

数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇

4725
来自专栏phodal

我的职业是前端工程师【三】:学习前端只需要三个月【语言篇】

过去,我一直无法相信:一个新人在三个月里可以学好前端。后来,我信了。因为三个月后,我又是一个前端的新人,我又需要重新入门前端。 前端领域好似也有一个“摩尔定律”...

2049
来自专栏华章科技

100个iOS开发/设计程序员面试题汇总,你将如何作答?

无论是对于公司还是开发者或设计师个人而言,面试都是一项耗时耗钱的项目,本文作者CameronBanga从编程、设计、AppStore等各个方面对iOS开发者及设...

814
来自专栏十月梦想

Vue项目初体验--vue实现去哪网儿应用

慕课网Vue学习,vue实战项目,去哪网实例,在线演示地址 去哪网Vue项目在线演示

2472
来自专栏FreeBuf

一枚邪恶的输入法浅析

“输入法”或许是计算机软件领域最伟大的发明之一了,也是我们日常的计算机使用中最常用到的软件,一款好的输入法能让我们事半功倍。随着计算机的软硬件不断发展,输入法的...

2046
来自专栏全栈工程师成长之路

全栈开发自学日志(持续更新)

3197
来自专栏HT

数百个 HTML5 例子学习 HT 图形组件 – 拓扑图篇

HT 是啥:Everything you need to create cutting-edge 2D and 3D visualization. 这口号是当年...

5325
来自专栏Jerry的SAP技术分享

在同一页面显示多个JavaScript统计图表

最近我接到一个开发任务,要求就“售后服务客户满意度调查问卷表”里客户填写的反馈答案做一个统计。

1492

扫码关注云+社区

领取腾讯云代金券