本文首發於政采雲前端團隊博客:Flutter For Web 編譯的兩種方案

要問現在最火的移動端的框架是什麼,每個人心中自有自己的答案。不過就筆者人而言,前端開發所做的更多是在顯卡上繪製每一個像素的藝術。從這一出發點來看,Flutter 基於瀏覽器上的 DOM 樹、安卓的 View、IOS 的 UIVeiw,從底層的自建渲染引擎來構建我們的應用 UI,並提供相關接口。目前 Flutter 關注度還是比較高的,Flutter 的熱度已經超越⽼牌跨平台框架 React Native。不過吹捧了那麼多,可能就會有小夥伴們要問了,Flutter 到底是個什麼東西。接下來我們就一起來認識它。
Flutter 原理簡介Flutter 是由 Google 推出的開源的高性能跨平台框架,一個 2D 渲染引擎。在 Flutter中,Widget 是 Flutter 用戶界面的基本構成單元,可以說一切皆 Widget。下面來看下 Flutter 框架的整體結構組成。
Flutter 框架的設計如下所示:

Flutter 框架是一個分層的結構,每個層都建立在前一層之上。
Framework(框架層):這是一個純 Dart 實現的 SDK;
【Foundation】在最底層,主要定義給其他層使用的底層工具類和方法。
【Animation】是動畫相關的類。
【Painting】封裝了 Flutter Engine 提供的繪製接口,例如繪製縮放圖像、插值生成陰影、繪製盒模型邊框等。
【Gesture】提供處理手勢識別和交互的功能。
【Rendering】是框架中的渲染庫。
【Widgets 】是 Flutter 提供的的一套基礎組件庫。Material 和 Cupertino 是兩種視覺風格的組件庫。
Flutter 在移動端的實踐中,目前來說已經有很成熟的業界方案了,但是 Flutter 在 web 的環境裡面的應用還是有所欠缺的。今天我們先來研究下 Flutter 構建 web 程序的相關技術棧。
用於 Web 支持的兩個方案其實,最早在 2018 Flutter 1.0 的時候,Flutter 的產品經理 Tim Sneath 就推出了 Flutter Web。Flutter Web 想在單代碼庫的情況下,讓 Flutter 應用擁有 Web 支持。開發者可以使用 Dart 編寫應用並部署到任意的 Web 服務器上,或嵌入到瀏覽器中。甚至其他的 IOS、安卓、windows 設備,開發者都可以使用 Flutter 所具有的特性,也不需要特殊的瀏覽器插件支持。在 Flutter Web 的設計之初,主要考慮了兩個方案用於 Web 支持:
方案 1:具有最好的兼容性,它優先考慮 HTML + CSS 表達,當 HTML + CSS 無法表達圖片的時候,會使用 Canvas 來繪製。但 2D Canvas 在瀏覽器中是位圖表示,會造成像素化下的性能問題。
方案 2:是新的 Web API , 屬於 CSS Houdini (https://developer.mozilla.org/zh-CN/docs/Web/Guide/Houdini)的組成部分。CSS Houdini 提供了一組可以直接訪問 CSS 對象模型的 API ,使得開發者可以去書寫代碼並被瀏覽器作為 CSS 加以解析,這樣在無需等待瀏覽器原生的支持下,創造了新的 CSS 特性。它的繪製並非由核心 JavaScript 完成,而是類似 Web Worker 的機制。但目前 CSS Paint API 不支持文本,此外各家廠商對其支持也並不統一。
Flutter for Web 的兩種編譯器Flutter 官方給我們提供了 dart2js 和 dartdevc 兩個編譯器,我們不僅可以將代碼直接運行在 chrome 瀏覽器,也可以將 Flutter 代碼編譯為 js 文件部署在服務端。
1、dart2js 編譯器我們在調用 flutter run build 命令後會將項目的 main.dart 傳入編譯流程,最終輸出的是構建產物中的 .dill 文件 。這個 .dill 文件很關鍵,筆者的理解是一種包含了 dart 程序的抽象語法樹生成的 AST (http://caibaojian.com/ast.html)文件,能運行在所有的操作系統和 CPU 架構上。
在構建過程中 Flutter_tools 首先會將傳入的參數進行組裝,然後調用 dart2jsSnapshot。進行 dart 文件編譯,生成 Weget 樹的二進制文件的 .dill 文件,這個代碼的位置在 dart-sdk/html/dart2js/html_dart2js.dart 路徑下(對應版本:Flutter 2.5.3 Tools • Dart 2.14.4)。
dart2jsSnapshot 是一個專門為 web 平台轉換做的解釋器,類似於 Flutter Web_sdk。只不過 Flutter Web_sdk 的源碼更多的是在調試時候做 debugger,效率很低。在 build 的時候,顯然利用快照的方式比較合理。
dart2js 編譯流程:

dart2js 調用的快照文件示例圖:

具體執行看這裡:https://dart.dev/tools/dart2js
我們再來看下 build 之後的生成目錄:

通過上面的介紹,我們知道整個轉換流程中承上啟下的關鍵產物就是 .dill 文件。那麼他是如何通過代碼生成的呢?
我們,首先通過 Flutter_tools (https://github.com/flutter/flutter/tree/master/packages/flutter_tools) 調用到 dart2jsSnapshot 文件。調用的參數如下:
--libraries-spec=/Users/beike/Flutter/bin/cache/FlutterWeb_sdk/libraries.json--native-null-assertions-Ddart.vm.product=true-DFlutterWeb_AUTO_DETECT=true--no-source-maps//是否生成sourcemap的選項;-O1-o--cfe-only//代表只完成前端編譯,生成kernel文件後就不繼續下面的後端編譯流程。/Users/beike/path_to_js/main.dart.js/Users/beike/path_to_dill/app.dill其中 O1 代表優化等級,dart2js 支持 O0 - O4 共 5 種優化,O4 的優化程度最高。通過優化可以減少產物的大小並且優化代碼的性能。
Dart2js 的後端編譯主要包括以下代碼:
JsClosedWorld 代表了通過 closed-world 語義編譯之後的代碼。它的結構如下:
classJsClosedWorldimplementsJClosedWorld{staticconstStringtag='closed-world';@overridefinalNativeDatanativeData;@overridefinalInterceptorDatainterceptorData;@overridefinalBackendUsagebackendUsage;@overridefinalNoSuchMethodDatanoSuchMethodData;FunctionSet_allFunctions;finalMap<classentity,Set>mixinUses;Map<classentity,List>_liveMixinUses;finalMap<classentity,Set>typesImplementedBySubclasses;finalMap<classentity,Map>_subtypeCoveredByCache=<classentity,Map>{};//TODO(johnniwinther):Canthisbederivedfrom[ClassSet]s?finalSetimplementedClasses;finalSetliveInstanceMembers;//Membersthatarewritteneitherdirectlyorthroughasetterselector.finalSetassignedInstanceMembers;@overridefinalSetliveNativeClasses;@overridefinalSetprocessedMembers;...}在 dartdevc 我們不僅可以將代碼直接運行在 chrome 瀏覽器,也可以將 flutter 代碼編譯為 js 文件部署在服務端。如果代碼運行在 chrome 瀏覽器,flutter_tools 會使用 dartdevc 編譯器進行編,如下圖:

dartdevc 是支持增量編譯的,開發者可以像調試 Flutter Mobile 代碼一樣使用 hot reload 來提升調試效率。Flutter for Web 調試也是非常方便的,編譯後的代碼是默認支持 source map,當運行在 web 瀏覽器時,開發者是不用關心生成的 js 代碼是怎樣的。
好了,接下來我們從一個簡單的案例 (https://gitee.com/suckson/flutter-web-test)入手,看看 Flutter,是如何一步一步將 web 轉換為我們的 js,並在瀏覽器中使用和繪製出一個頁面。
關鍵代碼部分:
Widgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:Text(widget.title,style:TextStyle(color:Colors.white),),),body:Center(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:<Widget>[Container(width:250,height:250,color:Colors.orange,child:Center(child:Text("6",style:TextStyle(fontSize:200.0,color:Colors.green,fontWeight:FontWeight.bold),),),)],),),//Thistrailingcommamakesauto-formattingnicerforbuildmethods.);}abstractclassc{voiddrawRect(Rectrect,Paintpaint);}html.HtmlElement_drawRect(ui.Offsetp,SurfacePaintDatapaint){[省略部分代碼]Element=_drawRect(paint);//繪製,[省略部分代碼]finalStringcssTransform=float64ListToCssTransform(transformWithOffset(_canvasPool.currentTransform,p).storage);imgElement.style..transformOrigin='000'..transform=cssTransform..removeProperty('width')..removeProperty('height');rootElement.append(imgElement);_children.add(imgElement);returnimgElement;}當調度任務調用到 drawRect() 方法之後,drawRect() 方法中會創建 canvas 元素,並且將 dart 的繪製邏輯重新實現一遍,最終將 Element 添加到 rootElement,也就是當前的 flt-canvas 元素中。生成的 html 如下:
dart2js 和 dartdevc 本質上是一件事情,但這兩種編譯器是應用在不同場景。在開發應用程序時選擇 dartdevc,它支持增量編譯,因此你可以快速查看編輯結果。在構建要部署的應用程序時,選用 dart2js,它使用搖樹等技術來生成優化的且精簡的代碼。
dart2js 提供了更快的編譯時間,並且編譯後的運行效果與之前相比更加一致、完整,更重要的是,輸出的代碼更加整潔。Dart 團隊正在努力使 dart2js 編譯後的代碼比手寫 JS 更快地運行。
通過以上的簡單分析,我們發現通過 Flutter 的編譯,重寫了大量的繪製的 Class,這對於前端開發來說可能提供了一個新的思路。當然本次有些地方還是很粗略的分析。只是初步介紹了 Flutter 打包構建流程,並沒有給出完整的思路。後面會繼續努力,將在後續的文章中與大家分享。希望隨着 Flutter 社區方案的愈加完善,利用 Flutter 技術棧上線的 web 產品也會越來越多。
引用