做者|嚴康 京東 ARES編輯|王文婧ARES 做爲京東技術中臺的多端融合技術團隊,聚焦於跨端開發技術框架和平臺搭建,包括但不限於 RN、Flutter、小程序等技術棧。目前已經普遍應用於京東商城、京東金融、京東到家、京東拼購等京東 La 系核心 APP 內,幫助業務團隊低成本、快速開發本身的業務,以應對市場的瞬息萬變之勢。css
Google Flutter 是一個很是優秀的跨端框架,不只能夠運行在 Android、 iOS 平臺,並且能夠支持 Web 和桌面應用。在國內小程序是很是重要的技術平臺,咱們也一直思考可否把 Flutter 擴展到小程序端?咱們團隊以前已經開源了 Alita 項目,Alita 能夠把 React Native 的代碼轉換並運行在微信小程序平臺。受此啓發,咱們認爲一樣是聲明式 UI 框架的 Flutter 一樣能夠運行在小程序平臺。html
因此,咱們發起了 flutter_mp 開源項目。以微信小程序爲例,不過現階段,flutter_mp 項目還處於早期的實驗階段,不少功能還在探索規劃中,歡迎你們在 Github 上隨時關注咱們的最新進展,或者參與項目共同探索。git
原理簡介雖然還有諸多功能未完成,咱們先來談談整個flutter_mp的實現原理。篇幅緣由,下面咱們將只對flutter_mp幾個重要的部分進行簡單說明。github
先看下 flutter_mp 的實際效果:web
Flutter 版官方 layout 樣例canvas
經過 flutter_mp 轉換並運行在小程序端效果小程序
聲明式 UI 的處理Flutter是聲明式 UI 框架,聲明式 UI 只須要向框架描述 UI 長什麼樣子而不用關心框架具體的實現細節,具體到Flutter,上層的 UI 描述使用底層的 skia 圖形引擎處理就是原生Flutter,而把底層處理換成 html/css/canvas 就是flutter_web,而flutter_mp則是探索在類小程序上對這些 UI 描述的處理。微信小程序
咱們看一個最簡單例子:var x = 'Hello World'對於上面的 UI 結構,咱們只須要在小程序的 wxml 文件裏,用以下的結構對應就 OK 了。
Center(
child: Text(x)
);
// wxml 部分雖然實際的結構要比上面的狀況複雜的多,不過經過上面簡單的例子,咱們知道起碼要作兩個事情:
<Center>
<Text>{{x}}</Text>
</Center>
// js 部分
Component({
data: {
x: 'Hello World'
}
})
收集 wxml 渲染須要的數據,放置到小程序組件的 data 字段。設計模式
咱們知道小程序是沒法動態操做節點的,wxml 結構須要預先生成,因此Flutter運行在小程序以前,會存在一個編譯打包階段,這個階段會遍歷 Dart 代碼,根據必定規則生成 wxml 文件(編譯階段還會作下文將要提到的另一個重要事情 --- 把 Dart 編譯爲 js)。瀏覽器
具體的,咱們首先會將 Dart 源碼處理爲可分析的 AST 結構,AST 是源代碼的樹型表示結構。而後咱們深度遍歷這份 AST 語法樹結構,生成目標 wxml,整個過程以下:
構建 wxml 結構的難點在於:Flutter 不只是聲明式 UI 仍是「值 UI」,什麼叫「值 UI」?簡單來講,Flutter 把 UI 當作是一個普通的值,相似於字符串,數字同樣的值,既然是一個普通的值,就能夠參與全部的控制流程,能夠是函數的返回值也能夠是函數參數等等。而小程序的 wxml 雖然也是聲明式 UI,卻不是「值 UI」,wxml 更加像模版,更加的靜態。怎麼用靜態的 wxml 表達動態的「值 UI」是構建 wxml 結構的關鍵所在。
看個例子:Widget getX() {這裏的 child: x,x 是一個動態值,它的具體值須要在運行階段才能肯定,它多是任意的 Widget,如何在靜態的 wxml 上處理這裏動態的 x?受 Alita 框架的啓發,這裏主要是藉助於小程序 template 的動態性(template 的 is 屬性能夠接受變量值)。有以下幾步:
if (condition1) {
return Text('Hello');
} else if (condition2) {
return Container(
child: ...
);
} else if (condition3) {
return Center(
child: ...
);
}
...
}
Widget x = getX();
Center(
child: x // < --- 如何處理這裏的 x??
);
<template name="template001">
<text>Hello</text>
</template>
<template name="template002">
<Container>...</Container>
</template>
<template name="template003">
<Center>...</Center>
</template>
在遇到相似 x 這種動態值的時候,固定地會生成一個 template 佔位。
<template name="template004">
<Center>
<template is="{{templateName}}" data="{{...templateData}}"/>
</Center>
<template name="template003">
在運行階段,會根據 getX 函數的運行結果來決定 x 映射的「UI 值」,若是 getX 裏面 condition1 爲 true,那麼這裏的 templateName 的值就是 template001。具體的數據計算收集工做,參考下面的 「渲染數據收集」過程。
能夠看出 flutter_mp 處理「值 UI」方式,徹底參考了 Alita。
渲染數據收集wxml 結構的生成是在編譯階段就完成了,與它不一樣渲染數據是運行時的信息,隨時會根據 setState 而改變。那麼咱們怎麼收集出咱們須要的渲染數據呢?
若是咱們仍是順着 Flutter 的架構圖,很難插入咱們收集的鉤子函數,另外 Flutter 的這個架構對於小程序來講過重了,下圖紅框裏的這些過程對於小程序的渲染來講並沒必要要。最後因爲最終的代碼會被轉化爲 js,而 Flutter 自己依賴的庫裏面不少是不支持轉化 js 的,好比 dart:ui 等等。
因此咱們實現了一個極簡極簡的 Flutter 小程序版本 mini_flutter,在編譯期咱們會把全部對 Flutter 庫的引用替換爲 mini_flutter, mini_flutter 只存在到上圖的 Rendering 階段,這個 Rendering 的實現也是爲小程序定製的, 在運行時期 Rendering 不斷收集 Widgets 的信息。最終生成一個 UI 描述的 JSON 結構,這個結構就包含了上文所說的 templateName , templateData,UI 描述將會被下層小程序得到,用來渲染小程序 UI,架構圖以下:
Dart/JS:轉化與互操做Flutter的開發語言是 Dart,而小程序的運行環境是瀏覽器,因此咱們還須要把 Dart 編譯爲 JavaScript 代碼。
在上文的編譯打包階段也提到這一點,這個過程主要是使用了 Dart 提供的 dart2js 工具,不過,針對小程序環境,生成的 js 代碼仍須要作一些適配,另外雖然都是 JS 代碼,dart2js 生成的 js 和小程序原生 js 的運行環境倒是隔離的,也就是說它們是不能共享變量,方法等等,它們各自在自己的"域"裏執行。
這帶來兩個問題:Widget 初始化 或者 setState 更新,生成的 UI 描述 JSON,如何傳遞給小程序"域"呢?
相關渲染回調,事件的都發生在小程序"域",這些信息如何傳遞給 Dart?
總結一下:Dart(最終會編譯爲 JS)與小程序原生 JS 如何互操做?
解決這個問題主要是藉助 dart:js, package:js 這兩個庫:
Dart 操做 JS:import 'package:js/js.dart';
@JS("JSON.stringify")
external stringify(String str);
這樣當 Dart 代碼調用 stringify 方法的時候,實際上會執行window.JSON.stringify
方法。
// dart 註冊
void main() {
context['dartHi'] = () {
print('dart hi!');
};
}
// js 調用
window.dartHi()
這裏只是簡單說明 Dart 與 JS 的互操做,另外因爲小程序的運行環境是閹割之後的瀏覽器環境,flutter_mp的實現還稍有不一樣。
總之,Dart 與 JS 是能夠互操做的,這樣就打通了上層Flutter環境和下層小程序環境。
佈局系統Flutter的佈局系統不一樣與 css,可是和 css 頗類似。
在上文提到的 Rendering 階段,會根據 Widget 的佈局屬性、類別、約束條件生成一個等效的 css 樣式。注意,這裏邊界約束是上下文相關的。好比一個沒有寬高的 Container 實際大小,不只和子元素相關,還和父元素傳遞過來的邊界約束條件相關,這個實際上是比較麻煩的,能不能把 Flutter 的 Widget 屬性,邊界約束徹底用 css 表達,咱們還在尋求有效的方案。
總結和flutter_web同樣,徹底把Flutter全部特性渲染到小程序上是不可能的,通常咱們以爲應該是部分頁面,部分功能須要運行在小程序上,這樣使用flutter_mp纔是有意義的。
正如前文所說,flutter_mp還在很早期的階段,社區的支持和反饋對咱們來講特別寶貴。同時歡迎廣大開發者一塊兒來維護flutter_mp。
flutter_mp: https://github.com/areslabs/flutter_mp
若是你須要在生產環境實現小程序跨端開發,推薦使用咱們成熟的 RN 轉小程序項目 Alita。
Alita:https://github.com/areslabs/alita
課程推薦Java 工程師除了 Java 語法,還須要哪些知識?點亮 Java 開發技能樹,全面掌握:設計模式、流行框架、組件和工具、分佈式架構、微服務、性能優化。清除能力短板,突破進階關口。點擊「閱讀原文」或掃描「下圖二維碼」瞭解 Java 工程師的學習路徑。