嚴康 資深前端工程師,負責JDReact框架前端及小程序轉(zhuǎn)換引擎開發(fā) CMO體系-商城前臺產(chǎn)品研發(fā)部-基礎(chǔ)平臺研發(fā)部-技術(shù)平臺綜合組-多端融合平臺組 臧國東前端工程師,負責JDReact框架前端及小程序轉(zhuǎn)換引擎開發(fā) CMO體系-商城前臺產(chǎn)品研發(fā)部-基礎(chǔ)平臺研發(fā)部-技術(shù)平臺綜合組-多端融合平臺組 概述JDReact是CMO體系商城前臺產(chǎn)品研發(fā)部推出的多端融合開發(fā)框架。經(jīng)過不斷的技術(shù)完善,目前已經(jīng)在手機京東客戶端累計接入 100+ 業(yè)務,穩(wěn)定支撐 千萬 級DAU,并對外支持 15+ 個獨立APP,擁有完善的 API 和功能強大的開發(fā) IDE 工具。 本文重點介紹了JDReact提供的小程序雙向轉(zhuǎn)換工具的原理及用法,通過此工具可以把已經(jīng)開發(fā)的微信小程序低成本轉(zhuǎn)換成JDReact應用,也支持把現(xiàn)有JDReact業(yè)務低成本轉(zhuǎn)換成微信小程序應用,完全實現(xiàn)了JDReact和微信小程序生態(tài)的打通。 背景 此項目的最初靈感來源于我們團隊今年5月份參加第六屆黑客馬拉松大賽并獲得冠軍的項目“微信小程序一鍵轉(zhuǎn)換工具” http://tech.jd.com/techical/questionDetail?id=771 (京東內(nèi)網(wǎng))
因為我們在進行項目開發(fā)時,常常會遇到以下情況: 01 場景一:已經(jīng)開發(fā)微信小程序,遷移到APP 項目之初,為了更好的利用微信的流量,更加方便的推廣,我們會先直接發(fā)布一個小程序,等到后來我們的用戶越來越多,應用也變得越來越復雜。這個時候想要把這個應用獨立出來,把客戶掌握在自己手里,進一步定制應用。此時,沒有其他辦法,我們只能叫上Android,IOS開發(fā)人員,叫上之前的產(chǎn)品經(jīng)理,之前的測試把之前小程序的功能再重新在原生上實現(xiàn)一遍。
02 場景二:已經(jīng)開發(fā)APP,遷移至小程序生態(tài) 也可能,我們現(xiàn)在已經(jīng)有了獨立App,現(xiàn)在由于種種原因(流量的需求,運營的需要),需要發(fā)布一個小程序的版本。怎么辦呢?同樣我們只能叫上小程序開發(fā)人員,之前的產(chǎn)品經(jīng)理,之前的測試在復制一個小程序的版本。 03 場景三:新業(yè)務開發(fā),技術(shù)選型中 或者,我們現(xiàn)在即將開始一個新的項目,這個項目既有獨立App也有小程序版本(或者可見的未來會有兩個版本)。 那么我們是不是需要保持原生團隊, 小程序團隊,從而進行兩個版本的開發(fā)呢?
如果我們可以把JDReact的應用轉(zhuǎn)化為小程序,把小程序轉(zhuǎn)化為JDReact應用,那么我們就可以低成本的把原來的JDReact項目/小程序項目移植到另一端了。而且新開始的項目我們就可以根據(jù)人員配置只開發(fā)一個JDReact的版本或者小程序的版本,等未來需要的時候,直接轉(zhuǎn)化為對應的另一端。
由于只需要維護一端的版本,就可以大大的降低軟件工程師的工作,同時產(chǎn)品,測試的工作量也會相應的減輕很多。另外, 我們希望轉(zhuǎn)化之后的代碼具有良好的可讀性, 方便再次開發(fā)與修改。 效果演示 我們先用一個實際的例子來展示下轉(zhuǎn)化工具的效果, 我們利用JDReact轉(zhuǎn)換工具將 “值得買京東優(yōu)選”的微信小程序 轉(zhuǎn)化為對應的 JDReact版本 并運行在 手機京東客戶端中 ; 首先看一下微信小程序版“值得買京東優(yōu)選” 轉(zhuǎn)化引擎將會遍歷尋找小程序源代碼目錄中的wxml,進行轉(zhuǎn)化期間會合并其對應的wxss, json, js文件。 轉(zhuǎn)化完成之后,啟動生成的JDReact原代碼目錄,運行模擬器查看效果。 以下是運行中手機京東客戶端中的JDReact版“值得買京東優(yōu)選” 原理介紹 不管是React應用還是小程序應用都可以表達為:ui = f(data)。并且他們提供很相似的數(shù)據(jù)更新方式,小程序是 setData(newData, cb) , React是 setState(newState,cb) ,這兩個基本條件是我們轉(zhuǎn)化引擎的前提,基于此前提,轉(zhuǎn)化工作理論上是可行的。 f在React里面可以簡單的理解為JSX,在小程序里面可以理解為wxml。wxml是小程序提供的“靜態(tài)”的書寫ui的方式靈活性比較低。JSX是react提供的方式,很靈活,里面可以嵌入任何表達式,本質(zhì)上就是JS。如果我們可以把JSX代碼翻譯為等效的wxml代碼,把wxml代碼翻譯為等效的JSX代碼,那么我們就有能力實現(xiàn)兩種應用的轉(zhuǎn)化。
顯然,我們的引擎必須能夠“讀懂”代碼,為了實現(xiàn)這個目標,首先我們將代碼轉(zhuǎn)化為AST格式,然后根據(jù)相應規(guī)則不斷的修改AST結(jié)構(gòu),最后生成新的代碼。通過babel-parse(把源代碼解析為AST),babel-traverse(遍歷操作AST),babel-generator(生成新代碼)來實現(xiàn)對源代碼的操作。這個相應規(guī)則源自于兩端的等效寫法。 比如說: wx:for 和 Array.map的對應, wx:if和邏輯表達式的對應。
然而,并不是所有的規(guī)則都這么顯而易見 比如JSX 對于這種情況我們會不斷遍歷JSX表達式,如果發(fā)現(xiàn)是函數(shù)調(diào)用,將會用“返回值替換”, 也就是會用getView的返回值來替換對應JSX表達式,替換的時候需要處理好數(shù)據(jù)綁定。 再比如下的JSX wxml的變量綁定“{{}}”是不能出現(xiàn)函數(shù)調(diào)用(wxs除外)的,這種情況,我們將會使用“表達式前置”, 也就是相應表達式的值提前放置在小程序的data中,轉(zhuǎn)化之后的wxml可能如下: 遍歷AST的時候,需要不斷的判斷 JSX表達式 是否需要前置。最后轉(zhuǎn)化之后的data會保護很多這樣的var 由于JSX的足夠靈活,在進行JSX轉(zhuǎn)向wxml,我們將會有很多類似的轉(zhuǎn)化規(guī)則。 那是不是 wxml轉(zhuǎn)JSX就一帆風順呢? 也不是, 首先一個問題。babel-parse并不識別wxml代碼格式。對于wxml,我們需要預處理wxml, 使其可以被parse識別。另外wxml的很多奇怪表現(xiàn)也是我們轉(zhuǎn)化的時候需要兼容的。 比如: 小程序 的data里面并沒有a屬性,更別說b屬性,c屬性。但是這個在小程序里面是表現(xiàn)正常的,而且很常見。我們不希望轉(zhuǎn)化之后的程序在這種情況下報錯,我們對這種表達式進行了容錯,react-native(預計0.56版本)支持optional-chaining之后,我們也會跟進用optional-chaining來改造這種情況。 wxml到JSX的轉(zhuǎn)化,我們已經(jīng)基本完成。JSX到wxml的轉(zhuǎn)化已經(jīng)覆蓋所有常見的寫法。 隨著越來越多的規(guī)則被添加進來,我們轉(zhuǎn)化引擎能夠覆蓋的情況將會越來越多。 對齊兩端組件 wxml與JSX的雙向轉(zhuǎn)化成功,是轉(zhuǎn)化引擎的第一步。但是轉(zhuǎn)化引擎應用于實際項目還有一段距離,因為不管是小程序項目還是JDReact項目都不可能只有View, Text組件, 即使我們把 users && <FlatList/> 轉(zhuǎn)化為小程序 <FlatList wx:if="{{users}}"/> 也是沒有作用的,小程序根本就不認識FlatList。 要想讓小程序認識FlatList,我們需要在小程序端實現(xiàn)一個小程序版的FlatList,好在發(fā)展到今天,小程序的自定義組件已經(jīng)很完善。 意味著我們需要對齊兩端組件,需要在小程序端實現(xiàn)一套JDReact的組件庫,包括FlatList, SectionList,JDImage,JDSwiper等,同時實現(xiàn)組件的對應屬性。 在React Native端,我們也必不可少的需要實現(xiàn)一套這樣的小程序組件,包括 form,radio, radio-groupd等。實際上出于對齊屬性的考慮,包括view/View, text/Text這些基本組件,也是通過在另外一端實現(xiàn)對應組件這種方式實現(xiàn)的。 對齊小程序組件庫:
對齊React Native 和 JDReact組件庫:
生命周期和事件 data驅(qū)動視圖, 生命周期和事件提供了對data修改的時機。小程序的組件提供了與React相似的生命周期 小程序自定義組件生命周期:
React的生命周期: 對于兩端意義相同的生命周期,比如ready和componentDidMount,會在遍歷AST的時候進行修改,對于那些React存在,小程序不存在的生命周期我們會在小程序調(diào)用setData前后進行模擬。 另外,小程序的Page具有和組件不一樣的生命周期,其中有些比如 onShow,onHide需要和導航器配合實現(xiàn)。 小程序的事件系統(tǒng)源自于web,而RN是自己有一套獨立的手勢系統(tǒng),這兩種有一定差異。 明顯的,小程序的每一個組件都可以響應事件,而RN的組件一般只是Touchable** 系列的組件響應事件。 對于這種情況,我們會檢測每一個小程序組件,一旦發(fā)現(xiàn)組件響應了事件,就給對應的RN組件加上手勢系統(tǒng), 另外一個比較大的差異,RN的事件是不冒泡的。 除了這些差異, 兩邊的事件基本是可以對應上的,比如bindtap對應onPress。處理方式和生命周期大同小異。 如果說React Native轉(zhuǎn)化為小程序難點是要處理JSX的靈活,那么小程序項目轉(zhuǎn)化為React Native的坑就是樣式了。小程序的wxss源自于css,基本上是css的全集。而React Native采用Yoga作為樣式布局系統(tǒng),Yoga是基于C實現(xiàn)的一套Flexbox布局系統(tǒng)。 所以,在進行小程序樣式轉(zhuǎn)化時,原有的小程序wxss代碼必須進行適配才可以接入到RN項目中,產(chǎn)生效果,適配過程主要需要解決下面幾個問題。 1. RN不支持CSS選擇器在 React Native中為一個元素指定某種樣式,只可采用如下方式: 在React Native中,只可以通過為某元素明確style來賦予樣式,在小程序以及web中,樣式賦予則非常的靈活,作為一個簡單的例子, 此時只需要在對應的css文件中寫 入 在這個例子中,我們用到了css提供的元素選擇器(div),類選擇器(.a)。css提供了數(shù)十種選擇器,功能十分強大。然而RN中卻沒有支持任何一種選擇器,因此在進行小程序樣式轉(zhuǎn)化前,首先要考慮如何適配小程序的css的選擇器功能。 css提供了數(shù)十種選擇器,且各類選擇器間的組合非常靈活,而究其根本,其最基本元素僅有五種 其余類似于后代選擇器之類則可以看作連接符,例如對 于 因此大多數(shù)的CSS組合可以看作 [基本元素,連接符,基本元素…] 的形式,考慮到這一點后,我們進一步研究發(fā)現(xiàn),其實所有基本類型選擇器都可以由某個標簽的標簽名,以及prop屬性來獲取,而所有連接符關(guān)系,都可以通過元素在小程序wxml文件中的文檔結(jié)構(gòu)來進行計算匹配,我們通過抽象語法樹的方式解析wxml文件,為每個元素注入了它自身在文檔結(jié)構(gòu)中的信息,來進行選擇器的計算適配,目前已經(jīng)提供了近10種最常用的選擇器類型,且功能在不斷的完善與擴展。 2. CSS寫法的不一致RN與小程序?qū)τ贑SS中的寫法差異較大。選擇器方面,小程序CSS中選擇器名可以為相對隨意的字符串,例如’test-a¥b’也是有效的選擇器名,而在RN中,這并不是一個有效的變量命名,因此我們在RN中,我們將所有的選擇器名定位字符串類型,例如上述選擇器名將轉(zhuǎn)為 因此可以完整適配小程序中任意的命名方式。 另一方面,在屬性上存在寫法不一致的情形。例如,小程序中寫為border-width,而適配到RN中,則需要轉(zhuǎn)化為borderWidth,不僅如此,對于一些簡寫的屬性,例如小程序CSS中的 有著明確的語意,然而在RN中,無法解析這樣的語法,我們也對此進行了轉(zhuǎn)化,例如對于上述情形,我們在RN中解析并轉(zhuǎn)化為了 在RN中與小程序還有眾多寫法不一致的情形,對此我們盡最大可能提供了支持,并給出了規(guī)范。 3. 在RN與CSS中存在屬性默認值的不同RN與小程序CSS存在很多屬性默認值的不同,這就導致了,即使選擇器適配功能完好,同樣的CSS代碼,在小程序上表現(xiàn)正常,RN上則顯示不正確。 比如,RN中采用flex布局,其flex方向默認為列布局,而在小程序CSS則默認為行布局。又如,RN中的flexShrink默認值為0,小程序CSS中則為1,這會導致頁面展示的不正常。 對此,我們提供了適配方案,首先我們會對小程序開發(fā)者提出一些基本要求,例如必須采用flex布局方式。另一方面,我們會對于每個RN中與小程序CSS中默認值存在差異的情況進行修正,盡可能讓小程序開發(fā)者不改變自己的CSS寫法。對于上述兩種情形,我們都會提供出具體的規(guī)范。 我們仔細研究了小程序CSS與RN中CSS的不同,并在最大程度上適配了小程序CSS的寫法,讓用戶可以自由使用小程序CSS的各項功能,這一切都是為了讓開發(fā)者獲得更好的開發(fā)體驗。 另外,為了提供更好的服務,我們制定了具體的規(guī)范,確保小程序開發(fā)者在現(xiàn)有規(guī)范下開發(fā)完成后,轉(zhuǎn)化前與轉(zhuǎn)化后頁面展示完全一致。 css轉(zhuǎn)化流程總結(jié)如下: 限制與約束 有的時候, 知道我們“做不到什么”更加重要。 由于AST只是靜態(tài)分析代碼,許多“運行時”才能夠得到的信息是得不到的,比如: 這種情況,我們根本不知道 a 到底是什么, “返回值替換” 就會出問題。 又比如: 這里的ForFun會直接導致我們判斷React組件失敗,代碼需要自律! React中的高價組件暫時不支持轉(zhuǎn)換,并且我們目前只支持React Native官方組件和JDReact通過的組件。 兩邊系統(tǒng)的差異和限制,在小程序端,比如小程序的包大小要在2M以內(nèi), 那么當JDReact轉(zhuǎn)化過來的小程序打完包也必須在2M以內(nèi), 比如小程序的tab頁個數(shù),路由深度也是有限制的, 另外,前文提到的,在小程序向React應用轉(zhuǎn)化的時候,對小程序本身所使用的樣式是有限制的。 在RN端,小程序的scroll-view是可以上下左右滾動的,而RN的只可以一個方向, 事件處理的差異等等。 對于所有的這些限制和約束,我們后續(xù)會給出一份完整的清單,同時也會給出相應的替換方案。 合作共贏 目前我們已經(jīng)初步實現(xiàn)了反向轉(zhuǎn)換引擎,正向轉(zhuǎn)換引擎也正在加速開發(fā)中。非常歡迎有興趣的個人或團隊和我們交流合作,合作試用可以在公眾號下方留言ERP或聯(lián)系方式?。?! |
工作日 8:30-12:00 14:30-18:00
周六及部分節(jié)假日提供值班服務