南麓-前端高性能渲染新思路 Web On Flutter
前端早早聊大會,前端成長的新起點,與掘金聯(lián)合舉辦。加微信 codingdreamer 進(jìn)大會專屬周邊群,贏在新的起跑線。
第二十五屆|前端述職專場,晉升/述職/匯報/年終總結(jié) - 前端每年都要過的關(guān),5-9 全天直播,7 位講師(螞蟻/有贊/字節(jié)/創(chuàng)業(yè)公司等等),點我上車?? (報名地址):

關(guān)鍵詞有:
前端述職需要如何包裝自己? 前端晉升為何拿不到好的結(jié)果? 前端晉升在工作中需要注重哪些產(chǎn)出? 前端有沒有比較好的工作匯報方法論
所有往期都有全程錄播,可以購買年票一次性解鎖全部
正文如下
本文是第十七屆 - 前端早早聊框架專場,也是早早聊第 117 場,來自飛豬-南麓 的分享
前言
當(dāng)前,前端技術(shù)日新月異,僅看移動端上的渲染方案。從早期的 H5 Wap,到借助客戶端能力通過離線包、prefetch、JSBridge 提升性能和擴展功能的 Hybrid 方案,再到前幾年大火的以 Weex/ReactNative 為代表的的大前端融合渲染方案,以及最近幾年各大廠商陸續(xù)推出的商業(yè)價值大于技術(shù)價值的小程序方案。前端技術(shù)的選型已由純粹的性能追求,演變到性能與效能、甚至業(yè)務(wù)價值的博弈,而每個新技術(shù)的誕生,也都有其背后的場景價值。
本文作者將就 Flutter 這一新的客戶端渲染方案和讀者分享一下對下一代高性能前端渲染思路 Web On Flutter 的思考??纯?Flutter 之于 Web 能碰撞出什么樣的火花。
初識 Flutter
這里先給不太了解 Flutter 的讀者做一下簡單介紹:Flutter 是由 Google 于2017年推出并開源的一個移動應(yīng)用開發(fā)框架。其一大特點是基于 Skia 實現(xiàn)了一套自繪引擎,可以同時運行在移動端、 IOT 等多種平臺。Flutter 使用 Dart 開發(fā),Dart 語言的一個特點是既支持 JIT(即時編譯)又支持 AOT(提前編譯),如此便可在開發(fā)階段采用 JIT 模式進(jìn)行高效開發(fā),同時在發(fā)布階段享受 AOT 模式的高性能。
作為 Google 出品的拳頭級產(chǎn)品,F(xiàn)lutter 一經(jīng)推出便大受歡迎,目前在 Github 中有超過 10W的 star,平均每月1.8次(2020年數(shù)據(jù))的 Stable 版本迭代也足見維護力度。此外,F(xiàn)lutter 也備受大廠青睞,阿里、騰訊、字節(jié)、美團等都在開展相關(guān)布局建設(shè),而在阿里內(nèi)部,也有淘寶、閑魚、飛豬、盒馬等多個 BU 落地了實際業(yè)務(wù)。
Flutter 的優(yōu)勢
那么是什么原因讓 Flutter 如此受追捧呢,我們對比一下當(dāng)前移動端主流的渲染方案:

可以看到 Flutter 兼具 Native 的高性能和 WebView 的低開發(fā)成本,同時又因為自繪而具備極佳的渲染一致性,因此也就不難解釋為何大家都對 Flutter 抱有如此大的想象和期待。
Flutter 的技術(shù)特點
我們再簡單介紹一下 Flutter 的技術(shù)特點:
自繪引擎
Flutter 基于 Skia 這一跨平臺圖形庫自建了一套渲染管線,而不是使用系統(tǒng)(Android、iOS)的原生控件或者 WebView 的渲染管線。

自繪帶來的好處顯而易見,根本上解決了跨端一致性問題(這里可以對比 Weex,Weex 是將渲染一致性問題轉(zhuǎn)移到容器層解決,但這也導(dǎo)致了容器的愈加難以維護)。
響應(yīng)式框架
前面提到 Flutter 本質(zhì)是一個開發(fā)框架,而從開發(fā)模式上說,它是一個響應(yīng)式框架,這也是 Flutter 開發(fā)高效的一個原因(這里我們暫且忘記它的組件地獄嵌套吧~)。我們看個示例,一個簡單的“數(shù)字增加”組件,在 Flutter 里可以這么實現(xiàn):

對于前端同學(xué)來說是否有種熟悉感,沒錯,這個寫法和 JSX 非常相似!包括組件嵌套和狀態(tài)更新。**
一切皆widgets
“Widget 是 Flutter 應(yīng)用程序用戶界面的基本構(gòu)建塊。每個 Widget 都是用戶界面一部分的不可變聲明。與其他將視圖、控制器、布局和其他屬性分離的框架不同,F(xiàn)lutter 具有一致的統(tǒng)一對象模型:Widget?!鄙厦孢@段是 Flutter 官網(wǎng)的原話,翻譯到前端語境,Widget 既可以是 div、span 這種結(jié)構(gòu)組件,也可以是 padding、opacity 這種樣式組件,同時也可以是 dialog 這種功能組件......

而這些 Widgets 會根據(jù)布局形成一個層次結(jié)構(gòu),也就是一棵 Widgets 樹,這點讀者可以先留個印象,在后面的介紹中會有用到。
Flutter 與 Web
前文中,我們對 Flutter 有了初步了解,也簡單對比了它和其他主流移動端渲染方案的優(yōu)劣勢。在本節(jié)中,我們將重點看一下 Flutter 對 Web(或者說前端)的沖擊,及可能碰撞的火花。
Flutter 對前端的沖擊與結(jié)合
一直以來,前端相較于客戶端,比較大的優(yōu)勢體現(xiàn)在以下4個方面:
人力成本:1 vs 2(iOS + Android) 開發(fā)效率:JIT vs AOT 投放場景:跨端 vs 單端 迭代頻次:隨時 vs 發(fā)版
但是與 Flutter 相比,前端的“人力成本”和“開放效率”優(yōu)勢將極大減弱,不過依然保留著跨端投放和高頻迭代的優(yōu)勢,那么我們是否可以將二者的優(yōu)勢結(jié)合起來呢?答案便是 Web On Flutter。

Web On Flutter 技術(shù)思路
既然是以 Web 的方式開發(fā),用 Flutter 引擎渲染,那就意味著渲染流程的前半段是 Web,而后半段是 Flutter。如何橋接二者的渲染管線便成了破題的重點,這里可以有3個切入點:

切入點A:用 Web 的 DOM 模擬 Flutter 的 Widget。這個方案對前端開發(fā)有很強的約束,需要遵循 Flutter 的組件思維來開發(fā)頁面。目前社區(qū)里的 MXFlutter 便是這個思路。 切入點B:用 Flutter 的 Widget 模擬 Web 的 DOM。這個方案的難點在于精準(zhǔn)的樣式映射,比較適合限定(W3C標(biāo)準(zhǔn)子集)的前端場景。目前飛豬的 Flugy 方案采用的正是這個思路。 切入點C:將 Web 的 DOM 樹直接映射到 Flutter 的 RenderObject 樹上。這個方案的好處是,相比和 Widgets 樹的橋接,將 DOM 樹直接橋接到 RenderObject 樹可以更細(xì)力度的操作,理論上樣式還原的上限會更高點。但因其是在 Flutter 內(nèi)部的 RenderObject 上進(jìn)行了擴展,所以也會對 Flutter 版本有更大的敏感度。目前手淘的 Kraken 便是這個思路。 切入點C Plus:這個切入點在圖中沒有表現(xiàn),其實是對 Flutter 的更深入改造,既重寫 Flutter Rendering 層,好處是不用擔(dān)心 DOM 樹和 RenderObject 樹的不對齊,但相應(yīng)的開發(fā)成本也是巨大的。目前手淘的 Unicorn 在嘗試這方面的探索。
通過下圖,讀者可以進(jìn)一步理解這 4 種思路的區(qū)別:

Web On Flutter 實現(xiàn)原理
本節(jié)將以 Widget 模擬 DOM 的思路為例,分析一下可行的實現(xiàn)原理。我們先通覽一下該思路下的整體渲染鏈路:

前面提到整條鏈路的關(guān)鍵點就是將 Web 渲染鏈路和 Flutter 渲染鏈路橋接起來,那橋接的第一步就是可以雙向通信,這里涉及的關(guān)鍵技術(shù)點就是 JS Binding;而后就是用 Widget 來模擬 DOM,生成最后用來繪制的 Widgets 樹,這里需要關(guān)注的技術(shù)關(guān)鍵點包括** **DOM 樹映射及 CSS 樣式映射;同時我們也需要關(guān)注到如何進(jìn)行事件綁定。接下來我就以上述 4 個技術(shù)關(guān)鍵點分別進(jìn)行介紹。
JS Binding
在 WebView 中,我們常采用重寫 Alert / Prompt、攔截 URL 或 API 注入等方案來進(jìn)行 JS 和 Native 的通信。在 Web On Flutter 中,JS 和 Flutter 的通信也可以采用類似 API 注入的方案。也就是 Flutter 通過 JS 引擎 向 JS 全局上下文掛載變量,比如一個 function,而后 JS 便可以通過該 function 調(diào)到 Flutter 的方法,反過來,F(xiàn)lutter 也可以直接通過 JS 引擎訪問到 JS 全局上下文中的變量。如此雙向通信便可建立起來,下面用張圖來直觀的展示一下這個過程:

這里擴展補充一下上圖的 C++ 膠水層的含義。Dart 代碼可以通過 dart:ffi 庫來調(diào)用本地的 C API,但 JS 引擎又只能被 C++ 調(diào)用,所以我們需要編寫一層 C++ 膠水層來封裝所需使用的 JS 引擎 API,再通過 extern C 標(biāo)記來編譯成 C 產(chǎn)物給到 Dart 來調(diào)用。
再進(jìn)一步分析整條通信鏈路,可以看到一大瓶頸是跨語言通信(JS <=> C/C++ <=> Dart),作者曾做過實驗,JS 無參調(diào) Dart 單次耗時在 0.05ms 左右,Dart 無參調(diào) JS 則需要 0.08ms 左右,只看單次確實很快,但是考慮到一個真實的頁面的渲染指令數(shù)可能在 1000 量級,并且攜帶大量參數(shù),所以最終耗時會很容易超過 100ms,所以我們還需要進(jìn)一步優(yōu)化,可行的方案如緩存渲染指令進(jìn)行批量調(diào)用等,這里就不做展開了。
DOM 樹映射
我們再來看一下 Web 中的 DOM 樹如何轉(zhuǎn)成 Flutter 中的 Widgets 樹。我們知道 Dom 樹在前端是通過createElement、appendChild 等 DOM API 來進(jìn)行創(chuàng)建的,整個創(chuàng)建過程實質(zhì)就是一條條渲染指令。
對于createElement 指令,F(xiàn)lutter 在接收到后,會根據(jù)所要創(chuàng)建的 Element 的特性,用多個 Widget 組合出來。比如 body 元素,最外層需要一個 Container Widget 來包裹以便設(shè)置一些通用樣式,body 的子節(jié)點是縱向排布的,所以還需要一個 Column Widget,最后考慮到它是可滾動的,還需要一個SingleChildScrollView 來支持。注意這里模擬 body 的 Widgets 組合只是簡單示例一下,實際實現(xiàn)要考慮到多種情況會復(fù)雜的多。
對于 appendChild 指令,還記得前面提到 Flutter 是響應(yīng)式框架嗎,我們可以類比在 React 中增刪組件,用 setState 的方式來維護一個節(jié)點的子節(jié)點。
整體的流程可以參看下圖:

CSS 樣式映射
在完成 DOM 樹到 Widgets 樹轉(zhuǎn)換之后,我們需要考慮如何將 CSS 樣式在 Flutter 中還原,還記得前面提到的渲染指令嗎,除了告知 Flutter 需要創(chuàng)建什么類型的 Element,它還會攜帶這個元素的屬性,其中就包括了樣式(通過 Webpack 等工具將 CSS 轉(zhuǎn)成內(nèi)聯(lián)樣式)。我們所需要做的就是將這些樣式用一個或多個 Wdiget 來還原實現(xiàn),這里我們舉兩個例子,一個是絕對定位,一個是底部對齊(比如價格和¥符):
Flutter 提供了一個 Stack Widget,允許子組件堆疊,而 Positioned Widget 可以根據(jù) Stack 的四個角來確定自身位置,如此便可以模擬 Web 中的絕對定位。
底部對齊的方案有很多,我們這里說一下 Flex 方案,幸運的是 Flutter 提供了 Flex 組件,可以通過屬性設(shè)置很容易模擬 flex-direction、align-items 樣式。至于彈性空間的分配(flex: 1),也有Expanded Widget 可以模擬。
另外一些通用樣式,比如寬、高、背景色、邊框、圓角可以在 Container Widget 中設(shè)置;文本顏色、字體大小可以在 Text Widget 中設(shè)置;圖片填充模式可以在 DecorationImage Widget 中設(shè)置等等。
我們再通過下圖的一個常見商品卡片示例看下在 Flutter 中如何進(jìn)行樣式還原:
事件綁定
最后再看一下事件綁定如何來做:
在 Web 代碼中,我們通過 Node.addEventListener(event, callback) 來監(jiān)聽 DOM 交互事件,而這個監(jiān)聽 API 會轉(zhuǎn)成 addEvent(nodeId, event) 指令調(diào)到 Flutter; Flutter 在捕獲到用戶的交互事件后,通過 nodeId 找到并觸發(fā)綁定在 Web 層 Node 上的事件回調(diào);
該過程用一張圖展示如下:

另外,F(xiàn)lutter 有著和 Web 類似的從內(nèi)向外的事件冒泡機制,這也讓 Flutter 和 Web 之間的事件綁定更貼合,但可惜的 是 Flutter 沒有停止冒泡的機制,所以這塊還需我們自己去編碼模擬。
總結(jié)
我們做個簡單的回顧:
Flutter 因其高性能、渲染一致的特性(自繪引擎),加之較高的開發(fā)效率(Dart 的 JIT 模式 + 響應(yīng)式框架)和更低的人力成本(相比客戶端),廣受大家追捧; 面對 Flutter,前端的一些傳統(tǒng)優(yōu)勢變得微弱,但我們可以嘗試將兩者的優(yōu)點結(jié)合起來,也就是 Web On Flutter; Web On Flutter 的技術(shù)思路有多種,主要是看如何將 Web 的渲染流程和 Flutter 的渲染流程橋接起來; 這其中的技術(shù)關(guān)鍵點包括:JS Binding、Dom 樹映射、CSS 樣式映射、事件綁定;
順便打個廣告,作者所在的“飛豬-用戶前端和數(shù)字化經(jīng)營團隊”HC多多,歡迎各位對旅行感興趣,或者對 Flutter、Serverless 、微前端、一體化開發(fā)、端渲染、互動營銷、招選投搭、智能化、體驗技術(shù)、數(shù)據(jù)度量等等感興趣的同學(xué)加入我們。投遞郵箱:[email protected]。也歡迎關(guān)注我們的飛豬技術(shù)公眾號:Fliggy F2E,定期有高質(zhì)量文章更新喔。
別忘了 5-9 的第二十五屆|前端述職專場,晉升/述職/匯報/年終總結(jié) - 前端每年都要過的關(guān),7 位講師(螞蟻/有贊/字節(jié)/創(chuàng)業(yè)公司等等),點我上車?? (報名地址):

所有往期都有全程錄播,可以購買年票一次性解鎖全部
別忘了給文章點贊
