FlutterWeb效能優化探索與實踐

美團技術團隊發表於2021-12-20
美團外賣商家端基於 FlutterWeb 的技術探索已久,目前在多個業務中落地了App、PC、H5的多端複用,有效提升了產研的整體效率。在這過程中,效能問題是我們面臨的最大挑戰,本文結合實際業務場景進行思考,介紹美團外賣商家端在 FlutterWeb 效能優化上所進行的探索和實踐,希望對大家能有所幫助或啟發。

一、背景

1.1 關於FlutterWeb

時間回撥到 2018 年,Google 首次公開 FlutterWeb Beta 版,表露出要實現一份程式碼、多端執行的願景。經過無數工程師兩年多的努力,在今年年初(2021 年 3 月份),Flutter 2.0 正式對外發布,它將 FlutterWeb 功能併入了 Stable Channel,意味著 Google 更加堅定了多端複用的決心。

圖1 FlutterWeb歷史

當然 Google 的“野心”不是沒有底氣的,主要體現在它強大的跨端能力上,我們看一下 Flutter 的跨端能力在 Web 側是如何體現的:

圖2 Flutter跨端能力

上圖分別是 FlutterNative 和 FlutterWeb 的架構圖。通過對比可以看出,應用層 Framework 是公用的,意味著在 FlutterWeb 中我們也可以直接使用 Widgets、Gestures 等元件來實現邏輯跨端。而關於渲染跨端,FlutterWeb 提供了兩種模式來對齊 Engine 層的渲染能力:Canvaskit Render 和 HTML Render,下方表格對兩者的區別進行了對比:

圖3 模式對比

Canvaskit Render 模式:底層基於 Skia 的 WebAssembly 版本,而上層使用 WebGL 進行渲染,因此能較好地保證一致性和滾動效能,但糟糕的相容性(WebAssembly 從 Chrome 57 版本才開始支援)是我們需要面對的問題。此外 Skia 的 WebAssembly 檔案大小達到了 2.5M,且 Skia 自繪引擎需要字型庫支援,這意味著需要依賴超大的中文字型檔案,對頁面載入效能影響較大,因此目前並不推薦在 Web 中直接使用 Canvaskit Render(官方也建議將 Canvaskit Render 模式用於桌面應用)。

HTML Render 模式:利用 HTML + Canvas 對齊了 Engine 層的渲染能力,因此相容性表現優秀。另外,MTFlutterWeb 對滾動效能已有過探索和實踐,目前能夠應對大部分業務場景。而關於載入效能,此模式下的初始包為 1.2M,是 Canvaskit Render 模式產物體積的 1/2,且我們可對編譯流程進行干預,控制輸出產物,因此優化空間較大。

基於以上原因,美團外賣技術團隊選擇在 HTML Render 模式下對 FlutterWeb 頁面的效能進行優化探索。

1.2 業務現狀

美團外賣商家端以 App、PC 等多元化的形態為商家提供了訂單管理、商品維護、顧客評價、外賣課堂等一系列服務,且 App、PC 雙端業務功能基本對齊。此外,我們還在 PC 上特供了針對連鎖商家的多店管理功能。同時,為滿足平臺運營訴求,部分業務具有外投 H5 場景,例如美團外賣商家課堂,它是一個以文章、視訊等形式幫助商家學習外賣運營知識、瞭解行業發展和跟進經營策略的內容平臺,具有較強的傳播屬性,因此我們提供了站外分享的能力。

圖4 業務形態

為了實現多端(App、PC、H5)複用,提升研發效率,我們於 2021 年年初開始著手 MTFlutterWeb 研發體系的建設。目前,我們基於 MTFlutterWeb 完成提效的業務超過了 9 個,在 App 中,能夠基於 FlutterNative 提供高效能的服務;在 PC 端和 Mobile 瀏覽器中,利用 FlutterWeb 做到了低成本適配,提升了產研的整體效率。

然而,載入效能問題是 MTFlutterWeb 應用推廣的最大障礙。這裡依然以美團外賣商家課堂業務為例,在專案之初頁面完全載入時間 TP90 線達到了 6s 左右,距離我們的指標基線值(頁面完全載入時間 TP90 線不高於 3s,基線值主要依據美團外賣商家端的業務場景、使用者畫像等來確定)有些差距,使用者訪問體驗有很大的提升空間,因此 FlutterWeb 頁面載入效能優化,是我們亟需解決的問題。

二、挑戰

不過,想要突破 FlutterWeb 頁面載入的效能瓶頸,我們面臨的挑戰也是巨大的。這主要體現在 FlutterWeb 缺失靜態資源的優化策略,以及複雜的架構設計和編譯流程。下圖展示了 Flutter 業務程式碼被轉換成 Web 平臺產物的流程,我們來具體進行分析:

圖5 FlutterWeb 編譯流程

  1. Framework、Flutter_Web_SDK(Flutter_Web_SDK 基於 HTML、Canvas,承載 HTML Render 模式的具體實現)等底層 SDK 是可被業務程式碼直接引入的,幫助我們快速開發出跨端應用;
  2. flutter_tools 是各平臺(Android、iOS、Web)的編譯入口,它接收 flutter build web 命令和引數並開始編譯流程,同時等待處理結果回撥,在回撥中我們可對編譯產物進行二次加工;
  3. frontend_server 負責將 Dart 轉換為 AST,生成 kernel 中間產物 app.dill 檔案(實際上各平臺的編譯過程都會生成這樣的中間產物),並交由各平臺 Compiler 進行轉譯;
  4. Dart2JS Compiler 是 Dart-SDK 中具體負責轉譯 JS 的模組,它將上述中間產物 app.dill 進行讀取和解析,並注入 Math、List、Map 等 JS 工具方法,最終生產出 Web 平臺所能執行的 JS 檔案。
  5. 編譯產物主要為 main.dart.js、index.html、images 等靜態資源,FlutterWeb 對這些靜態資源缺少常規 Web 專案中的優化手段,例如:檔案 Hash 化、檔案分片、CDN 支援等。

可以看出,要完成對 FlutterWeb 編譯產物的優化,就需要干預 FlutterWeb 的眾多編譯模組。而為了提升整體的編譯效率,大部分模組都被提前編譯成了 snapshot 檔案( 一種 Dart 的編譯產物,可被 Dart VM 所執行,用於提升執行效率),例如:flutter_tools.snapshot、frontend_server.snapshot、dart2js.snapshot 等,這又加大了對 FlutterWeb 編譯流程進行干預的難度。

三、整體設計

如前文所述,為了實現邏輯、渲染跨平臺,Flutter 的架構設計及編譯流程都具有一定的複雜性。但由於各平臺(Android、iOS、Web)的具體實現是解耦的,因此我們的思路是定位各模組(Dart-SDK、Framework、Flutter_Web_SDK、flutter_tools)的 Web 平臺實現並尋求優化,整體設計圖如下所示:

圖6 整體設計

  • SDK 瘦身:我們分別對 FlutterWeb 所依賴的 Dart-SDK、Framework、Flutter_Web_SDK 進行了瘦身,並將這些精簡版 SDK 整合合入 CI/CD(持續整合與部署)系統,為減小產物包體積奠定了基礎;
  • 編譯優化:此外,我們在 flutter_tools 中的編譯流程做了干預,分別進行了 JS 檔案分片、靜態資源 Hash 化、資原始檔上傳 CDN 等優化,使得這些在常規 Web 應用中基礎的效能優化手段得以在 FlutterWeb 中落地。同時加強了 FlutterWeb 特殊場景下的資源優化,如:字型圖示精簡、Runtime Manifest 隔離、Mobile/PC 分平臺打包等;
  • 載入優化:在編譯階段進行靜態資源優化後,我們在前端執行時,支援了資源預載入與按需載入,通過設定合理的載入時機,從而減小初始程式碼體積,提升頁面首屏的渲染速度。

下面,我們分別對各項優化進行詳細的說明。

四、設計與實踐

4.1 精簡 SDK

4.1.1 包體積分析

工欲善其事,必先利其器,在開始做體積裁剪之前,我們需要一套類似於 webpack-bundle-analyzer 的包體積分析工具,便於直觀地比較各個模組的體積佔比,為優化效能提供幫助。

Dart2JS 官方提供了 --dump-info 命令選項來分析 JS 產物,但其表現差強人意,它並不能很好地分析各個模組的體積佔比。這裡更推薦使用 source-map-explorer ,它的原理是通過 sourcemap 檔案進行反解,能清晰地反映出每個模組的佔用大小,為 SDK 的精簡提供了指引。下圖展示了 FlutterWeb JS 產物的反解資訊(截圖僅包含 Framework 和 Flutter_Web_SDK):

圖7 反解資訊

4.1.2 SDK 裁剪

FlutterWeb 依賴的 SDK 主要包括 Dart-SDK、Framework 和 Flutter_Web_SDK,這些 SDK 對包體積的影響是巨大的,幾乎貢獻了初始化包的所有大小。雖然在 Release 模式下的編譯流程中,Dart Compiler 會利用 Tree-Shaking 來剔除那些引入但未使用的 packages、classes、functions 等,很大程度上減少了包體積。但這些 SDK 中仍然存在一些能被進一步優化的程式碼。

以 Flutter Framework 為例,由於它是全平臺公用的模組,因此不可避免地存在各平臺的相容邏輯(通常以 if-else、switch 等條件判斷形式出現),而這部分程式碼是不能被 Tree-Shaking 剔除的,我們觀察如下的程式碼:

// FileName: flutter/lib/src/rendering/editable.dart
void _handleKeyEvent(RawKeyEvent keyEvent) {
  if (kIsWeb) {
    // On web platform, we should ignore the key.
    return;
  }
  // Other codes ...
}

上述程式碼選自 Framework 中的 RenderEditable 類,當 kIsWeb 變數為真,表示當前應用執行在 Web 平臺。受限於 Tree-Shaking 的機制原理,上述程式碼中,其它平臺的相容邏輯即註釋 Other codes 的部分是無法被剔除的,但這部分程式碼,對 Web 平臺來說卻是 Dead Code(永遠不可能被執行到的程式碼),是可以被進一步優化的。

圖8 部分功能構成

上圖展示了 SDK 的一部分功能構成,從圖中可以看出,FlutterWeb 依賴的這些 SDK 中包含了一些使用頻率較低的功能,例如:藍芽、USB、WebRTC、陀螺儀等功能的支援。為此,我們提供了對這些長尾功能的定製能力(這些功能預設不開啟,但業務可配置),將未被啟用長尾的功能進行裁剪。

通過上述分析可得,我們的思路就是對 Dead Code 進行二次剔除,以及對這些長尾功能做裁剪。基於這樣的思路,我們深入 Dart-SDK、Framework 和 Flutter_Web_SDK 各個擊破,最終將 JS Bundle 產物體積由 1.2M 精簡至 0.7M,為 FlutterWeb 頁面效能優化打下了堅實的基礎。

圖9 精簡成果

4.1.3 SDK 整合 CI/CD

為了提升構建效率,我們將 FlutterWeb 依賴的環境定製為 Docker 映象,整合入 CI/CD(持續整合與部署)系統。SDK 裁剪後,我們需要更新 Docker 映象,整個過程耗時較長且不夠靈活。因此,我們將 Dart-SDK、Framework、Flutter_Web_SDK 按版本打包傳至雲端,在編譯開始前讀取 CI/CD 環境變數:sdk_version(SDK 版本號),遠端拉取相應版本的 SDK 包,並替換當前 Docker 環境中的對應模組,基於以此方案實現 SDK 的靈活釋出,具體流程圖如下圖所示:

圖10 整合CI/CD

4.2 JS 分片

FlutterWeb 編譯之後預設會生成 main.dart.js 檔案,它囊括了 SDK 程式碼以及業務邏輯,這樣會引起以下問題:

  1. 功能無法及時更新:為了實現瀏覽器的快取優化,我們的專案開啟了對靜態資源的強快取,若 main.dart.js 產物不支援 Hash 命名,可能導致程式程式碼不能被及時更新;
  2. 無法使用 CDN:FlutterWeb 預設僅支援相對域名的資源載入方式,無法使用當前域名以外的 CDN 域名,導致無法享受 CDN 帶來的優勢;
  3. 首屏渲染效能不佳:雖然我們進行了 SDK 瘦身,但 main.dart.js 檔案依然維持在 0.7M 以上,單一檔案載入、解析時間過長,勢必會影響首屏的渲染時間。

針對檔案 Hash 化和 CDN 載入的支援,我們在 flutter_tools 編譯流程中對靜態資源進行二次處理:遍歷靜態資源產物,增加檔案 Hash(檔案內容 MD5 值),並更新資源的引用;同時通過定製 Dart-SDK,修改了 main.dart.js、字型等靜態資源的載入邏輯,使其支援 CDN 資源載入。

更詳細的方案設計請參考《Flutter Web在美團外賣的實踐》一文。下面我們重點介紹 main.dart.js 分片相關的一些優化策略。

4.2.1 Lazy Loading

Flutter 官方提供 deferred as 關鍵字來實現 Widget 的懶載入,而 dart2js 在編譯過程中可以將懶載入的 Widget 進行按需打包,這樣的拆包機制叫做 Lazy Loading。藉助 Lazy Loading,我們可以在路由表中使用 deferred 引入各個路由(頁面),以此來達到業務程式碼拆離的目的,具體使用方法和效果如下所示:

// 使用方式
import 'pages/index/index.dart' deferred as IndexPageDefer;
{
  '/index': (context) => FutureBuilder(
    future: IndexPageDefer.loadLibrary(),
    builder: (context, snapshot) => IndexPageDefer.Demo(),
  )
  ... ...
}

圖11 效果演示

使用 Lazy Loading 後,業務頁面的程式碼會被拆分到了多個 PartJS(對應圖中 xxx.part.js 檔案) 中。這樣看似解決了業務程式碼與 SDK 耦合的問題,但在實際操作過程中,我們發現每次業務程式碼的變動,仍然會導致編譯後的 main.dart.js 隨之發生變化(檔案 Hash 值變化)。經過定位與跟蹤,我們發現這個變化的部分是 PartJS 的載入邏輯和對映關係,我們稱之為 Runtime Manifest。因此,需要設計一套方案對 Runtime Manifest 進行抽離,來保證業務程式碼的修改對 main.dart.js 的影響達到最低。

4.2.2 Runtime Manifest抽離

通過對業務程式碼的抽離,此時 main.dart.js 檔案的構成主要包含 SDK 和 Runtime Manifest:

圖12 main.dart.js構成

那如何能將 Runtime Manifest 進行抽離呢?對比常規 Web 專案,我們的處理方式是把 SDK、Utils、三方包等基礎依賴,利用 Webpack、Rollup 等打包工具進行抽離並賦予一個穩定的 Hash 值。同時,將 Runtime Manifest (分片檔案的載入邏輯和對映關係)注入到 HTML 檔案中,這樣保證了業務程式碼的變動不會影響到公共包。藉助常規 Web 專案的編譯思路,我們深入分析了 FlutterWeb 中 Runtime Manifest 的生成邏輯和 PartJS 的載入邏輯,定製出如下的解決方案:

圖13 Runtime Manifest抽離

在上圖中,Runtime Manifest 的生成邏輯位於 Dart2JS Compiler 模組,在該生成邏輯中,我們對 Runtime Manifest 程式碼塊進行了標記,之後在 flutter_tools 中將標記的 Runtime Manifest 程式碼塊抽離並寫入 HTML 檔案中(以 JS 常量形式存在)。而在 PartJS 的載入流程中,我們將 manifest 資訊的讀取方式改為了 JS 常量的獲取。按照這樣的拆分方式,業務程式碼的變更只會改變 Runtime Manifest 資訊 ,而不會影響到 main.dart.js 公共包。

4.2.3 main.dart.js 切片

經過以上引入 Lazy Loading、Runtime Manifest 抽離,main.dart.js 檔案的體積穩定在 0.7M 左右,瀏覽器對大體積單檔案的載入,會有很沉重的網路負擔,所以我們設計了切片方案,充分地利用瀏覽器對多檔案並行載入的特性,提升檔案的載入效率。

具體實現方案為:將 main.dart.js 在 flutter_tools 編譯過程拆分成多份純文字檔案,前端通過 XHR 的方式並行載入並按順序拼接成 JavaScript 程式碼置於< script >標籤中,從而實現切片檔案的並行載入。

圖14 並行載入

4.3 預載入方案

如上一節所述,雖然我們做了很多工作來穩定 main.dart.js 的內容,但在 Flutter Tree-Shaking 的執行機制下,各個專案引用不同的 Framework Widget,就會導致每個專案生成的 main.dart.js 內容不一致。隨著接入 FlutterWeb 的專案越來越多,每個業務的頁面互訪概率也越來越高,我們的期望是當訪問 A 業務時,可以預先快取 B 業務引用的 main.dart.js,這樣當使用者真正進入 B 業務時就可以節省載入資源的時間,下面為詳細的技術方案。

4.3.1 技術方案

我們把整體的技術方案分為編譯、監聽、執行三個階段。

  1. 編譯階段,在釋出流水線上根據前期定製的匹配規則,篩選出符合條件的資原始檔路徑,生成雲端 JSON 並上傳;
  2. 監聽階段,在 DOMContentLoaded 之後,對網路資源、事件、DOM 變動進行監聽,並對監聽結果根據特定規則進行分析加權,得到一個首屏載入完成的狀態標識;
  3. 執行階段,在首屏載入完成之後對配置平臺下發的雲端 JSON 檔案進行解析,對符合配置規則的資源進行 HTTP XHR 預載入,從而實現檔案的預快取功能。

下圖為預快取的整體方案設計:

圖15 預快取方案設計

編譯階段

編譯階段會擴充套件現有的釋出流水線,在 flutter build 之後增加 prefetch build 作業,這樣 build 之後就可以對產物目錄進行遍歷和篩選,得到我們所需資源進而生成雲端 JSON,為執行階段提供資料基礎。下面的流程圖為編譯階段的詳細方案設計:

圖16 預快取編譯階段

編譯階段分為三部分:

  1. 第一部分:根據不同的釋出環境,初始化線上/線下的配置平臺,為配置檔案的讀寫做好準備;
  2. 第二部分:下載並解析配置平臺下發的資源組 JSON,篩選出符合配置規則的資源路徑,更新 JSON 檔案併發布到配置平臺;
  3. 第三部分:通過釋出流水線提供的 API,把 PROJECT_ID、釋出環境注入HTML檔案中,為執行階段提供全域性變數以便讀取。

通過對流水線編譯期的整合,我們可以生成新的雲端 JSON 並上傳到雲端,為執行階段的下發提供資料基礎。

監聽階段

我們知道,瀏覽器對檔案請求的併發數量是有限制的,為了保證瀏覽器對當前頁面的渲染處於高優先順序,同時還能完成預快取的功能,我們設計了一套對快取檔案的載入策略,在不影響當前頁面載入的情況下,實現對快取檔案的載入操作。以下為詳細的技術方案:

圖17 預快取監聽階段

在頁面 DOMContentLoaded 之後,我們會監聽三部分的的變化。

  1. 第一部分是監聽 DOM 的變化。這部分主要是在頁面發生 Ajax 請求之後,隨著MV模式的變動,DOM 也會隨之發生變化。我們使用瀏覽器提供的 MutationObserver API 對 DOM 變化進行收集,並篩選有效節點進行深度優先遍歷,計算每個 DOM 的遞迴權重值,低於閾值我們就認為首屏已載入完成。
  2. 第二部分是監聽資源的變化。我們利用瀏覽提供的 PerformanceObserver API,篩選出 img/script 型別的資源,在 3 秒內收集的資源沒有增加時,我們認為首屏已載入完成。
  3. 第三部分是監聽 Event 事件。當使用者發生 click、wheel、touchmove 等互動行為時,我們就認為當前頁面處於一個可互動的狀態,即首屏載入已完成,這樣會在後續進行資源的預快取。

通過上述步驟,我們就可以得到一個首屏渲染完成的時機,之後就可以實現預快取功能了。以下為預快取功能的實現。

執行階段

預快取的整體流程為:下載編譯階段生成的雲端 JSON,解析出需要進行預快取資源的 CDN 路徑,最後通過 HTTP XHR 進行快取資源進行請求,利用瀏覽器本身的快取策略,把其他業務的資原始檔寫入。當使用者訪問已命中快取的頁面時,資源已被提前載入,這樣可以有效地減少首屏的載入時間。下圖為執行階段的詳細方案設計:

圖18 預快取執行階段

在監聽階段,我們可以獲取到頁面的首屏渲染完成的時機,會獲取到雲端 JSON,首先判斷該專案的快取是否為啟用狀態。當該專案可用時,會根據全域性變數 PROJECT_ID 進行資源陣列的匹配,再以 HTTP XHR 方式進行預訪問,把快取檔案寫入瀏覽器快取池中。至此,資源預快取已執行完畢。

4.3.2 效果展示與資料對比

當有頁面間互訪問命中預快取時,瀏覽器會以 200(Disk Cache)的方式返回資料,這樣就節省了大量資源載入的時間,下圖為命中快取後資源載入情況:

圖19 預快取效果展示

目前,美團外賣商家端業務已有 10+ 個頁面接入了預快取功能,資源載入 90 線平均值由 400ms 下降到 350ms,降低了 12.5%;50 線平均值由 114ms 下降到 100ms,降低了 12%。隨著專案接入接入越來越多,預快取的效果也會越發的明顯。

圖20 預快取資料展示

4.4 分平臺打包

如前文所述,美團外賣商家業務大部分都是雙端對齊的。為了實現提效的最大化,我們對 FlutterWeb 的多平臺適配能力進行加強,實現了 FlutterWeb 在 PC 側的複用。

在 PC 適配過程中,我們不可避免地需要書寫雙端的相容程式碼,如:為了實現在列表頁面中對卡片元件的複用。為此我們開發了一個適配工具 ResponsiveSystem,分別傳入 PC 和 App 的各端實現,內部會區分平臺完成適配:

// ResponsiveSystem 使用舉例
Container(
  child: ResponsiveSystem(
    app: AppWidget(),
    pc: PCWidget(),
  ),
)

上述程式碼能較方便的實現 PC 和 App 適配,但 AppWidget 或 PCWidget 在編譯過程中都將無法被 Tree-Shaking 去除,因此會影響包體積大小。對此,我們將編譯流程進行優化,設計分平臺打包方案:

圖21 分平臺打包

  1. 修改 flutter-cli,使其支援 --responsiveSystem 命令列引數;
  2. 我們在 flutter_tools 中的 AST 分析階段增加了額外的處理:ResponsiveSystem 關鍵字的匹配,同時結合編譯平臺(PC 或 Mobile)來進行 AST 節點的改寫;
  3. 去除無用 AST 節點後,生成各個平臺的程式碼快照(每份快照僅包含單獨平臺程式碼);
  4. 根據程式碼快照編譯生成 PC 和 App 兩套 JS 產物,並進行資源隔離。而對於 images、fonts 等公用資源,我們將其打入 common 目錄。

通過這樣的方式,我們去除了各自平臺的無用程式碼,避免了 PC 適配過程中引起的包體積問題。依然以美團外賣商家課堂業務(6 個頁面)為例,接入分平臺打包後,單平臺程式碼體積減小 100KB 左右。

圖22 效果展示

4.5 圖示字型精簡

當訪問 FlutterWeb 頁面時,即使在業務程式碼中並未使用 Icon 圖示,也會載入一個 920KB 的圖示字型檔案:MaterialIcons-Regular.woff。通過探究,我們發現是 Flutter Framework 中一些系統 UI 元件(如:CalendarDatePicker、PaginatedDataTable、PopupMenuButton 等)使用到了 Icon 圖示導致,且 Flutter 為了便於開發者使用,提供了全量的 Icon 圖示字型檔案。

Flutter 官方提供的 --tree-shake-icons 命令選項是將業務使用到的 Icon 與 Flutter 內部維護的一個縮小版字型檔案(大約 690KB)進行合併,能一定程度上減小字型檔案大小。而我們需要的是隻打包業務使用的 Icon,所以我們對官方 tree-shake-icons 進行了優化,設計了 Icon 的按需打包方案:

圖23 圖示字型精簡

  1. 掃描全部業務程式碼以及依賴的 Plugins、Packages、Flutter Framework,分析出所有用到的 Icon;
  2. 把掃描到的所有 Icon 與 material/icons.dart(該檔案包含 Flutter Icon 的 unicode 編碼集合)進行對比,得到精簡後的圖示編碼列表:iconStrList;
  3. 使用 FontTools 工具把 iconStrList 生成字型檔案 .woff,此時的字型檔案僅包含真正使用到的 Icon。

通過以上的方案,我們解決了字型檔案過大帶來的包體積問題,以美團外賣課堂業務(業務程式碼中使用了 5 個 Icon)為例,字型檔案從 920KB 精簡為 11.6kB。

圖24 效果展示

五、總結與展望

綜上所述,我們基於 HTML Render 模式對 FlutterWeb 效能優化進行了探索和實踐,主要包括 SDK(Dart-SDK、Framework、Flutter_Web_SDK)的精簡,靜態資源產物優化(例如:JS 分片、檔案 Hash、字型圖示檔案精簡、分平臺打包等)和前端資源載入優化(預載入與按需請求)。最終使得 JS 產物由 1.2M 減少至 0.7M(非業務程式碼),頁面完全載入時間 TP90 線由 6s 降到了 3s,這樣的結果已能滿足美團外賣商家端的大部分業務要求。而未來的規劃將聚焦於以下3個方向:

  1. 降低 Web 端適配成本:目前已有 9+ 個業務藉助 MTFlutterWeb 實現多端複用,但在 Web 側(尤其是 PC 側)的適配效率依然有優化空間,目標是將適配成本降低到 10% 以下(目前大約是 20% );
  2. 構建 FlutterWeb 容災體系:Flutter 動態化包有一定的載入失敗概率,而 FlutterWeb 作為兜底方案,能提升整體業務的載入成功率。此外 FlutterWeb 可以提供“免安裝更新”的能力,降低 FlutterNative 老舊歷史版本的維護成本;
  3. 效能優化的持續推進:效能優化的階段性成果為 MTFlutterWeb 的應用推廣鞏固了基礎,但依然是有進一步優化空間的,例如:目前我們僅將業務程式碼和 Runtime Manifest 進行了拆離,而 Framework 及 三方包在一定程度上也影響到了瀏覽器快取的命中率,將這部分程式碼進行抽離,可進一步提升頁面載入效能。

閱讀美團技術團隊更多技術文章合集

前端 | 演算法 | 後端 | 資料 | 安全 | 運維 | iOS | Android | 測試

| 在公眾號選單欄對話方塊回覆【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可檢視美團技術團隊歷年技術文章合集。

| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行為,請傳送郵件至tech@meituan.com申請授權。

相關文章