[譯] Hummingbird: 構建Flutter Web應用
在今天的Flutter Live上,我們宣佈正嘗試在Web上執行Flutter。在這篇文章中,描述了我們如何應對嘗試過程中遇到的挑戰以及當前該技術的狀態。在本文的最後,您將找到有關互操作和嵌入的問題及答案;
讓我們快速回顧一下Flutter的架構。 Flutter是一個多層系統,更高的層級更方便開發者使用及操作,並允許開發者用更少的程式碼完成更多的功能,而較低的層級雖然為開發者提供更多的控制能力,然而這種控制也是有代價的:開發者必須處理一些更復雜的事項,當較高層不能滿足開發人員的需求時,他們可以深入到較低層。開發人員可以訪問Flutter Engine上方的所有層程式碼;
Flutter 移動端架構
Flutter Engine作為Flutter暴露出來的最底層庫,比如dart:ui。它對widgets,物理模擬(如重力等),動畫或佈局(文字佈局除外)一無所知。它所知道的是如何將圖片組合到螢幕上並將它們變成畫素呈現。在dart:ui上直接編寫應用程式是很困難的,這就是我們為開發者建立更高層的原因;
dart:ui之上的層級就是我們所謂的“框架”層。它下面的層級我們稱之為“引擎”。該框架完全使用Dart程式語言編寫。大多數引擎都是用C ++編寫的,特定於Android的部分用Java編寫,而iOS特定的部分用Objective-C編寫。 dart:ui中的一些基本類和函式是用Dart編寫的,主要用作Dart和C ++之間的橋樑。
Flutter還提供系統外掛。外掛是用一種語言編寫的程式碼,它可以直接訪問移動生態系統的OEM庫和第三方庫。要為Android建立外掛,您可以使用Java或Kotlin編寫程式碼。 iOS外掛是使用Objective-C或Swift程式碼編寫的。
Hello, The Web
Web平臺已經發展了數十年,包括許多技術和規範。有一些術語用於描述相關功能:HTML,CSS,SVG,JavaScript,WebGL。為了在Web上執行Flutter,我們需要:
-
編譯Dart程式碼:Flutter是用Dart編寫的,我們需要在Web上執行Dart
-
選擇要在Web上執行的Flutter子集:在Web上執行所有Flutter程式碼是不切實際也是毫無幫助的。其中一些是特定於具體平臺的,例如Android和iOS。
-
選擇足夠的Web功能子集:隨著時間的推移,Web平臺會累積功能重疊的功能。例如,您可以使用HTML + CSS,SVG,Canvas和WebGL繪製圖形。
從Dart語言誕生起,Dart語言就可以被編譯成JavaScript語言。許多重要的應用程式從Dart編譯為JavaScript,並已經被投放到生產環境中執行。 Flutter的編譯策略依賴於同樣的基礎設施。
當我們開始探索時,我們面臨著UI渲染的幾種選擇。我們很快意識到我們想要支援的特定Flutter層決定了我們將採用何種Web技術實現。我們製作了三個原型:
-
只是Widgets:這個原型實現了Flutter的Widget框架,並提供了一組核心佈局Widget作為構建自定義Widget的基礎。對於佈局和定位,它依賴於Web的內建功能,例如flexbox,網格佈局,瀏覽器滾動等。
-
Widgets+自定義佈局:此原型包括Flutter的佈局系統(由RenderObject提供),但將渲染物件直接對映到HTML元素。
-
Flutter Web Engine:這個原型保留了dart:ui之上的所有層,並提供了一個在瀏覽器中執行的dart:ui實現;
Flutter最有價值的功能之一是它可以跨平臺移植。您可以(我們鼓勵您)編寫自定義平臺特定程式碼,該程式碼可以共享到各跨平臺而不需要有任何差別。這允許使用單個程式碼庫編寫面向多個平臺的應用程式;
在嘗試將幾個示例應用程式移植到Web之後,我們意識到原型#1和#2不能提供Flutter開發人員喜歡(可接受)的可移植性級別。因此,我們決定使用原型#3:Flutter Web Engine,因為這將允許最高的框架級程式碼在不同平臺之間重用:
Flutter for the Web Architecture (Hummingbird)
既然知道了我們想要實現整個dart:ui API,我們需要選擇一組Web技術來構建。 Flutter一次呈現一幀UI。在每一幀內,Flutter構建Widget,執行佈局,最後在螢幕上繪製它們。
建立Widgets
Widget的構建機制不依賴於應用程式執行的環境。該過程只需要例項化記憶體中的物件並跟蹤它們的狀態,當狀態改變時,計算佈局和繪畫所需的最小更新。將此部分移植到Web上非常簡單,當Dart團隊在dart2js中實現了super-mixin支援之後,編譯器將所有widget和widget框架編譯為JavaScript時幾乎沒有任何問題;
佈局
佈局系統有點棘手。最大的挑戰是文字佈局。其他所有Widget - Center,Row, Column, Stack, Scrollable, Padding, Wrap等 - 這些widget都由框架佈局,因此無需修改即可編譯到Web上執行。
在Flutter中,您可以通過建立Paragraph物件並呼叫其layout()方法來放置一段文字。不幸的是,Web缺少直接的文字佈局API。我們用來測量文字佈局屬性的技巧是讓瀏覽器將其佈局,然後從DOM元素中讀回相關屬性。
放置文字段落時,Flutter測量段落的高度,寬度,最大內在寬度,最小內在寬度以及字母和表意基線(下圖黃色虛線)。這些屬性如下所示。
Paragraph layout attributes
您可以在Flutter的段落文件中找到更多詳細資訊。
要測量這些屬性,我們首先在HTML DOM元素中放置一個段落,然後我們讀取元素的尺寸。這會導致瀏覽器將其佈局。例如,要獲取元素的寬度和高度,我們呼叫offsetWidth及offsetHeight。為了測量基線,我們將段落放置在一個元素中,該元素配置為使用flex row進行自我佈局。在段落旁邊,我們放置另一個名為“探針”的元素。因為探針與文字的基線對齊,所以呼叫 ofollow,noindex">getBoundingClientRect 就可以得到基線。我們使用類似的技巧來測量最小和最大固有寬度。
Painting(繪製)
最後但同樣重要的是,我們需要繪製上述這些Widgets。我們在這方面的探索經歷了很多誤區,它仍然是我們研究中最活躍的領域之一。在幀渲染結束時,我們所有的widgets都需要在螢幕上變成畫素。在瀏覽器中,這意味著它們必須歸結為HTML / CSS,Canvas,SVG和WebGL的某種組合。
我們還沒有看過WebGL,主要是因為它是較底層級別的,並且要求我們重新實現瀏覽器已經可以做的事情,例如文字佈局和光柵化2D圖形,也因為我們還沒有弄清楚如何使用非Flutter元件的可訪問性,文字選擇和合成可以與WebGL一起使用。
我們的早期原型之一就是為每個RenderObject生成了一個HTML元素。我們確實獲得了有希望的結果,但結果卻證明了API的變化太大了。我們必須用Flutter維持一個巨大的程式碼增量,所以我們擱置了這個想法。
我們目前正在同時探索的兩種方法:
-
HTML+CSS+Canvas的組合
-
CSS Paint API
HTML+CSS+Canvas
通過這種方法,我們將框架生成的圖片分類為使用HTML + CSS表達的圖片,以及使用Canvas 2D表達的圖片。然後,我們輸出結合了HTML,CSS和2D畫布的HTML DOM。
我們更喜歡HTML + CSS,因為它有瀏覽器的顯示列表支援。這意味著我們可以優化圖片的光柵化在瀏覽器上的渲染引擎。這也意味著我們可以應用任意變換,尤其是旋轉和縮放,而不必擔心畫素化。我們將此畫布實現稱為“DomCanvas”。
如果我們無法使用HTML + CSS表達圖片,我們會回到畫布。 Canvas 2D允許我們繪製幾乎所有的Flutter繪圖命令。如果將Flutter的Canvas與Web的 CanvasRenderingContext2D 進行比較,您會發現許多相似之處,在畫布上繪畫是高效的,因為它不會建立需要隨時間維護的可變樹節點,如HTML DOM或SVG。
2D畫布的一個挑戰是瀏覽器將其表示為點陣圖,即儲存寬度x高度畫素的記憶體緩衝區。因此,縮放畫布會導致畫素化。如果縮放導致圖片大小的變形,我們需要調整畫布大小。我們發現分配畫布相當昂貴,因此調整它們的大小。最重要的是,當將多個畫布合成到同一頁面上時,瀏覽器必須執行柵格合成,合成柵格與顯示列表的工作方式不同。您可以將多個顯示列表繪製到同一個記憶體緩衝區中。我們呼叫Canvas 2D支援的canvas實現BitmapCanvas。我們正在研究使點陣圖畫布更有效的方法。
為了表達Flutter的 opacity, transform, offset, clip rect和其他圖層,我們使用純HTML元素。例如,不透明度層變為<flt-opacity>元素,其上具有不透明度CSS屬性,變換層變為帶有變換CSS屬性的<flt-transform>元素,而clip rect變為帶有overflow:hidden的<flt-clip-rect>。
完成所有操作後,幀將被渲染在作為HTML元素樹的頁面上呈現,其中DomCanvas和BitmapCanvas作為葉節點。例如:
Sample HTML DOM structure of a frame
Flutter Engine中的等效Flutter層級樹(稱為 流層 )如下所示:
Sample Flutter Engine layer structure
在結構上它們非常相似。最大的區別是,在Web上,我們必須根據內容選擇不同的圖片實現。
HTML + CSS + Canvas適用於所有現代瀏覽器。但是,我們已經在展望未來:
CSS Paint API
CSS Paint是一個新的Web API,是Houdini的一部分。 Houdini是指許多瀏覽器供應商之間的合作,旨在向開發人員展示CSS引擎的某些部分。特別是,CSS Paint API允許開發人員在這些元素請求繪製時將自定義圖形繪製成HTML元素。例如,您可以將元素背景的繪製任務分配給自定義CSS 畫筆(painter)完成。它與canvas非常相似,但有以下重要區別:
-
這個繪畫不是由主要的JavaScript獨立完成的,而是由一個叫做paint worklet的東西完成的。它有自己的記憶體空間,在提交DOM更改之後,在瀏覽器的繪製階段執行繪製工作。
-
CSS繪製由顯示列表支援,而不是點陣圖。這給了我們兩全其美的好處 - 兼顧2D畫布般的繪畫效率和沒有畫素化。
-
目前CSS繪畫不支援繪畫文字。
在撰寫本文時,Chrome和Opera是目前僅有的支援CSS Paint的瀏覽器。但是, 其他瀏覽器處於實現過程中的各個階段 。
我們在Web上執行Flutter中的CSS Paint API得到了實驗性支援,它已經顯示出良好的結果,特別是在效能方面。我們的實現只是將paint命令序列化為自定義CSS屬性。paint worklet讀取這些命令並執行它們。我們使用普通的<p>和<span> HTML元素渲染文字。
我們當前的序列化機制不是特別有效 - 它是一個將巢狀列表轉換為JSON的樹 - 但一部分Houdini專案的作法是新增對型別化陣列的支援。要讓它變成可用的話,我們需要將繪製命令編碼為型別化陣列而不是JSON字串。型別化陣列是可轉移的,這意味著它們可以通過引用從主隔離傳遞到paint工作,這個操作不涉及複製記憶體。
Interop and embedding(互操作及嵌入)
從Flutter呼叫Dart庫
Flutter Web應用程式可以完全訪問當今在Web上執行的所有現有Dart庫。
從Flutter呼叫JavaScript庫
Flutter Web應用程式完全支援Dart的JS-interop包:package:js和dart:js。
在Flutter Web應用程式中使用CSS
目前,Flutter假設完全控制網頁的正確性和效能。例如,我們只使用遵循某些效能指南的一小部分CSS,例如csstriggers.com/。在頁面上放置任意CSS可能會導致Flutter表現不可預測。
另一個在Flutter Web應用程式中避免使用CSS的原因是,在設計時,Flutter需要在渲染幀時知道所有佈局屬性。 CSS充當黑盒子的角色。例如,如果要顯示可滾動的widget列表,則必須例項化併為所有widget生成HTML並應用必要的CSS屬性(例如,flex-direction row和overflow:scroll)。然後瀏覽器將所有內容都放置排列出來並將其呈現在螢幕上。應用程式程式碼不參與佈局過程。
最後,本著保持Flutter程式碼可跨平臺移植的精神,我們避免使用CSS,因此我們可以在Android和iOS上執行相同的程式碼。
將Flutter嵌入現有的Web應用程式中
我們還沒有為此新增適當的支援,但我們打算在將來進行探索。我們正在考慮的幾種方法是使用<iframe>和shadow DOM。
在Flutter中嵌入非Flutter元件
我們還沒有新增對在Flutter Web應用程式中嵌入非Flutter元件 - 自定義元素,React元件,Angular元件 - 的支援,但我們打算在將來探討這一點。一種可能的途徑是使用平臺檢視將外來內容放入Flutter Web應用程式中。需要考慮的一個重要方面是這些外來內容可能對應用程式的效能和正確性產生何種影響。因為非Flutter元件可能包含任意CSS,如上所述,它可能會有問題。需要更多的研究。
可移植性
我們的目標是儘可能多地將flutter框架移植到Web上。但是,這並不意味著任何Flutter應用程式可以不用更改程式碼就可以在Web上執行。Flutter Web用程式仍然是一個Web應用程式;它在瀏覽器中被沙箱化,只能執行Web瀏覽器允許的操作。例如,如果您的Flutter應用程式使用到了沒有Web實現的Native 外掛,例如ARCore,您將無法在Web上執行該應用程式。同樣,它也沒有直接訪問檔案系統或底級網路的許可權。
當前狀態
我們構建了足夠的Web引擎來渲染大部分Flutter Gallery示例中的內容。我們還沒有移植Cupertino Widget,但所有Material Widgets,Material Theming,以及Shrine和Contact Profile演示應用程式都已經可以在Web上執行。(演示視訊地址)
原始碼放置在何處?
我們計劃很快開源這個專案,並很高興與開源社群分享。該專案最初是作為Google內部原始碼樹的一項探索而開始的。我們的程式碼穩定後,我們打算將開發轉移到GitHub,我們有機會將其與內部基礎架構分開,與此同時,如果您在 github.com/flutter 看到與Web相關的拉取請求,請不要感到驚訝!
結論
我希望這篇文章能讓您瞭解到我們正在解決的問題,以使Flutter在Web上良好執行。我們歡迎您的想法和主意。 請繼續關注Google I / O 2019!
原文連結
https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8
轉載請註明出處
Email:[email protected] 複製程式碼