扣丁書屋

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 是兩種視覺風格的組件庫。

  • Engine(引擎層):是 Flutter 的核心,這是一個純 C++ 實現的 SDK,其中包括了 Skia 引擎、Dart 運行時、文字排版引擎等。在代碼調用 dart:ui 庫時,調用最終會走到 Engine 層,然后實現真正的繪制邏輯。

  • Embedder(嵌入層):主要是將 Flutter 引擎 “安裝” 到特定平臺上,做好這一層的適配 Flutter 基本可以嵌入到任何平臺上去。

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 + Canvas
  2. CSS Paint API (https://zhuanlan.zhihu.com/p/39931190)優缺點:

方案 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 調用的快照文件示例圖:

如何生成 web 端代碼

具體執行看這里: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/Flutter Web_sdk/libraries.json 
--native-null-assertions
-Ddart.vm.product=true 
-DFlutter Web_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 的后端編譯主要包括以下代碼:

  1. 首先,編譯器會將傳入的 .dill 通過 BinaryBuilder 加載到 Component 中并存儲在 KernelResult 中;
 KernelResult result = await kernelLoader.load(uri); 

2 . computeClosedWorld() 方法會將第一步解析出來的所有 Library 解析成 JsClosedWorld。

 JsClosedWorld closedWorld = selfTask.measureSubtask("computeClosedWorld", () => computeClosedWorld(rootLibraryUri, libraries)); 

JsClosedWorld 代表了通過 closed-world 語義編譯之后的代碼。它的結構如下:

 class JsClosedWorld implements JClosedWorld {
    static const String tag = 'closed-world';
    @override final NativeData nativeData;
    @override final InterceptorData interceptorData;
    @override final BackendUsage backendUsage;
    @override final NoSuchMethodData noSuchMethodData;
    FunctionSet _allFunctions;
    final Map<classentity, Set> mixinUses;
          Map<classentity, List> _liveMixinUses;
    final Map<classentity, Set> typesImplementedBySubclasses; 
    final Map<classentity, Map> _subtypeCoveredByCache = <classentity, Map>{};
    // TODO(johnniwinther): Can this be derived from [ClassSet]s? 
    final Set implementedClasses;
    final Set liveInstanceMembers;
    // Members that are written either directly or through a setter selector.
    final Set assignedInstanceMembers;    
    @override final Set liveNativeClasses;
    @override final Set processedMembers;
    ...
  } 

3 . 然后,使用JsClosedWorld() 方法進行代碼優化,包括下面代碼中的 performGlobalTypeInference() 方法。

 GlobalTypeInferenceResults globalInferenceResults = performGlobalTypeInference(closedWorld); 

4 . 最終,generateJavaScriptCode() 方法會將上邊返回的結果通過 JSBuilder 生成最終的 js AST 也就是 .dill文件。

 generateJavaScriptCode(globalInferenceResults); 

2、dartdevc 編譯器

在 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,并在瀏覽器中使用和繪制出一個頁面。

關鍵代碼部分:

Widget build(BuildContext context) {
  return Scaffold(
    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),),
            ),
          )
        ],
      ),
    ), // This trailing comma makes auto-formatting nicer for build methods.
  );
}
abstract class c {
  void drawRect(Rect rect, Paint paint);
}
html.HtmlElement_drawRect(ui.Offset p, SurfacePaintData paint) {
 [省略部分代碼]
  Element = _drawRect(paint); // 繪制,
 [省略部分代碼]
 final String cssTransform = float64ListToCssTransform(
 transformWithOffset(_canvasPool.currentTransform, p).storage);
 imgElement.style
 ..transformOrigin = '0 0 0' ..transform = cssTransform 
 ..removeProperty('width')
 ..removeProperty('height');
 rootElement.append(imgElement);
 _children.add(imgElement); return imgElement;
 }

當調度任務調用到 drawRect() 方法之后,drawRect() 方法中會創建 canvas 元素,并且將 dart 的繪制邏輯重新實現一遍,最終將 Element 添加到 rootElement,也就是當前的 flt-canvas 元素中。生成的 html 如下:

Flutter 總結展望

dart2js 和 dartdevc 本質上是一件事情,但這兩種編譯器是應用在不同場景。在開發應用程序時選擇 dartdevc,它支持增量編譯,因此你可以快速查看編輯結果。在構建要部署的應用程序時,選用 dart2js,它使用搖樹等技術來生成優化的且精簡的代碼。

dart2js 提供了更快的編譯時間,并且編譯后的運行效果與之前相比更加一致、完整,更重要的是,輸出的代碼更加整潔。Dart 團隊正在努力使 dart2js 編譯后的代碼比手寫 JS 更快地運行。

通過以上的簡單分析,我們發現通過 Flutter 的編譯,重寫了大量的繪制的 Class,這對于前端開發來說可能提供了一個新的思路。當然本次有些地方還是很粗略的分析。只是初步介紹了 Flutter 打包構建流程,并沒有給出完整的思路。后面會繼續努力,將在后續的文章中與大家分享。希望隨著 Flutter 社區方案的愈加完善,利用 Flutter 技術棧上線的 web 產品也會越來越多。

引用

  1. Flutter渲染原理解析 (https://zhuanlan.zhihu.com/p/135969091)
  2. CSS Paint API (https://zhuanlan.zhihu.com/p/39931190)
  3. 如何評價 Flutter for Web?(https://www.zhihu.com/question/323439136/answer/850516697?ivk_sa=1024320u)
  4. Dart (https://dart.dev/get-dart)

https://mp.weixin.qq.com/s/LTzfMpWb3NiSqm_AAvKJhA

最多閱讀

如何有效定位Flutter內存問題? 1年以前  |  11676次閱讀
Flutter的手勢GestureDetector分析詳解 3年以前  |  7605次閱讀
Flutter插件詳解及其發布插件 2年以前  |  6293次閱讀
在Flutter中添加資源和圖片 3年以前  |  5144次閱讀
Flutter 狀態管理指南之 Provider 3年以前  |  4341次閱讀
發布Flutter開發的iOS程序 3年以前  |  4317次閱讀
Flutter for Web詳細介紹 3年以前  |  4190次閱讀
在Flutter中發起HTTP網絡請求 3年以前  |  3954次閱讀
使用Inspector檢查用戶界面 3年以前  |  3885次閱讀
Flutter Widget框架概述 3年以前  |  3261次閱讀
Flutter路由詳解 3年以前  |  3103次閱讀
JSON和序列化 3年以前  |  3026次閱讀
Flutter框架概覽 3年以前  |  2990次閱讀
推薦5個Flutter重磅開源項目! 1年以前  |  2951次閱讀
為Flutter應用程序添加交互 3年以前  |  2941次閱讀
處理文本輸入 3年以前  |  2844次閱讀
使用自定義字體 3年以前  |  2843次閱讀
編寫國際化Flutter App 3年以前  |  2831次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>