Flutter Web在美團外賣的實踐

1、背景

1.1 業務背景

美團外賣商家端業務形態

美團外賣商家端業務圍繞數百萬商家,在 PC 和 App 上分別提供了交易履約、運營、廣告、營銷等一系列功能,且常常有外投 H5 的場景(如外賣學院、商家社區、營銷活動等)。在這種多形態的業務場景下,如何保障多端體驗的一致性,以及如何提高多端迭代的效率,一直是商家端產研關注的重點。html

1.1.1 保障多端體驗一致性

因爲端能力的不一樣,致使了業務在 App 和 Web 上存在較大的表現差別,例如:App 上自帶動畫轉場,而在 Web 中的實現成本卻較高,每每也就降級捨棄了這部分功能。此外,即便咱們可利用公司內部的 Roo、MTDUI 等多端 UI 組件庫來儘可能抹平各端的 UI 差別,但因爲組件庫在各端的實現不盡相同,很難作到完美的一致性體驗。前端

1.1.2 提高多端迭代效率

因爲各端技術體系的不一樣,涉及多端的需求每每須要不一樣的開發、測試團隊各自完成開發、聯調、測試、上線等流程,佔用資源巨大,在各團隊不可並行支持的狀況下,甚至可能致使整個業務交付週期被拉長。雖然 React Native、Flutter 等跨平臺方案解決了一部分複用的問題,但顯然在商家端業務場景下是遠遠不夠的,咱們的目標是要達到全平臺(Android、iOS、PC、H5)複用,最大化地提高多端的迭代效率。ios

1.2 技術背景

1.2.1 Flutter 在美團外賣商家端的儲備

MTFlutter 是美團外賣搭建起的公司級 Flutter 研發生態,它的架構圖以下圖所示:web

MTFlutter 架構圖

如圖所示,MTFlutter 已涵蓋研發、調試、測試、發佈、線上運維及工程管理整套閉環,同時落地了動態化解決方案,支撐了公司多個業務發展。在大前端融合的趨勢下,美團外賣商家端持續在探索更優的多端複用方案,經過 MTFlutter 生態的建設,目前 Flutter 技術棧已覆蓋商家端 App 中 90%以上的業務,同時具有 Flutter 開發能力的同窗也達到 90% 以上。所以,在有足夠技術「儲備」的前提下,咱們可以基於 Flutter 作全平臺(Android、iOS、PC、H5)複用的探索。算法

1.2.2 Flutter Web 的支持

2018 年 Google 首次公開 Flutter Web Beta 版,旨在進一步實現一份代碼、多端運行的願景。目前,Flutter Web 已被正式合入 Master,期間通過無數工程師的努力,Flutter Web 已能提供與 Flutter Natvie 較統一的交互行爲和視覺體驗。docker

Flutter Native VS Flutter Web

如上圖可知,Flutter Web 與 Flutter Native 的總體架構類似,兩者共用 Framework 層(綠色部分),提供了包括動畫、手勢、基礎 Widget 類,以及大部分應用所需的 Material/Cupertino 主題 Widget 集合。區別在於:Flutter Web 重寫了 dart:ui 層(黃色部分),利用 DOM、Canvas 對齊了 Flutter Native 的 UI 渲染能力,使得 Flutter 編寫的 UI 可以在現代瀏覽器上正常展現。編程

此外,得益於 dart2js 這個早已成熟的工具,Dart 邏輯可以很容易的轉換爲 JavaScript,進而在 Web 中被正常運行。canvas

2、面臨的挑戰

綜上所述,咱們選擇基於 Flutter Web 探索跨端(App\PC\H5)解決方案,真正實現「Write Once & Run AnyWhere」。固然,面臨挑戰也是巨大的,主要體如今 Flutter 和 MTFlutter 現階段對 Web 支持還不是很充足。後端

2.1 Flutter Web 現狀

Google 官方目前對 Flutter Web 的工做主要還集中在 dart:ui(Web)的對齊,工程化和性能相關的事項作的還比較少,例如:api

  • Flutter Web 構建產物較簡陋,只是簡單的輸出 main.dart.js(1.1M,未 Gzip) 和 圖片等靜態資源,缺乏 JS 拆包、文件 Hash、資源上傳 CDN 等優化工做,極大影響了頁面的加載性能。
  • 因爲 Flutter Web 自身實現了一套頁面滾動機制,頁面滾動過程當中,會頻繁計算位置信息,引發滾動區域內容被從新建立,最終致使頁面滾動性能較差。

2.2 MTFlutter 現狀

雖然 MTFlutter 作了諸多 Flutter Native 層面的定製與優化,但在 Flutter Web 上的建設纔剛起步,具體表如今:

  • MTFlutter 現有的基礎依賴如:Request(請求封裝)、Router(路由)、埋點、容器橋、前端監控,還沒有支持在 Web 中的實現。
  • MTFlutter 已實現了完整的 Flutter Module 的打包發佈流程,但並不支持 Web 的構建與部署。

3、總體設計

MTFlutter 架構圖

上圖爲 MTFlutter + Web 架構圖,由圖可知 Flutter Web 頁面要知足投產要求,還有大量的工做(上圖黃色部分所示),主要包括:

  • 擴展基礎依賴(如:Request、Router、埋點等)在 Web 側的支持。
  • 完善工程化建設,例如:靜態資源優化、構建與部署自動化。
  • 深刻滾動性能與頁面加載性能優化,使得 Flutter Web 可以知足基本的投產要求。

4、詳細設計

4.1 基礎依賴建設

企業級應用的基礎開發依賴 (如:請求庫、路由庫、埋點庫等),要從新在 Flutter 中用 Dart 搭建一套,時間成本、兼容性、風險等都是不可控的。而 MTFlutter 是基於原有 Native 基礎依賴開發的 Plugin,所以並不支持 Web 端。此章節將展開介紹如何絲滑無感地擴展 MTFlutter 基礎依賴在 Web 端的實現。

4.1.1 Flutter Package 分平臺編程

在 Flutter 中經過使用 Package 能夠建立易於共享的模塊化代碼。官方強烈推薦使用 Package 形式管理各類工具方法。在官方定義中 Package 包含如下兩種類別:

  1. Dart Package:用 Dart 編寫的常規 Package,其中一些可能包含依賴於 Flutter 框架的特定功能,其使用範圍僅限於 Flutter,例如 path
  2. Plugin Package:用 Dart 編寫 API 多個平臺各自實現的特殊 Dart Package。Plugin Package 能夠爲 Android(使用 Kotlin 或 Java)、iOS(使用 Swift 或 Objective-C)、Web、macOS、Windows 或 Linux 或其任意組合編寫插件包。

下面分別對這兩種類型 Package 中如何分平臺編程進行介紹。

(1) Dart Package

Dart Package 是純 Dart 編寫,所以大部分代碼都可由 dart2js 直接編譯出 Web 平臺可運行的代碼,但某些涉及 Native 能力的庫 (如 dart:io)是沒法被轉譯的,所以須要有對平臺進行兼容的方法,下面介紹兩種在 Dart Package 中分平臺編程的方案。

代碼級別分平臺

針對代碼級別的分平臺,咱們能夠藉助 Flutter SDK 提供的一個常量 kIsWeb。使用方法以下:

查看源碼可知,kIsWeb 之因此能被用於判斷 Web 平臺,是利用了 JavaScript 不支持整型的特徵,在 Web 環境下,Dart 的 double 和 int 由相同類型的對象支持,浮點數 "0.0" 等於整數 "0",對於在 AOT 或 VM 上運行的 Dart 代碼卻並不是如此。
import 'package:flutter/foundation.dart';
if (kIsWeb) {
  print('Web 端')
} else {
  print('其餘端');
}

文件級別分平臺

針對文件級別分平臺,咱們利用條件導入導出,其中條件導出具體用法以下:

// tool.dart
export 'src/tool_native.dart' // 兜底導出,即沒有命中條件時導出的文件
  if (dart.library.html) 'src/tool_web.dart'; // web 端導出的文件,該文件中可使用 dart:html,也能夠經過判斷 dart.library.js 導出 Web 端文件。
// 引入 tool.dart
import 'package:tool/tool.dart';
void main() {
  print('import tool');
}

條件導入和條件導出相似,僅需將 export 改成 import 便可。在業務開發中這也是一種很是實用的分平臺編程方法。

(2) Plugin Package

Plugin Package (下文簡稱爲 Plugin) 在 Android 和 iOS 平臺都是經過 MethodChannel 實如今 UI 層和 Platform 層傳遞消息從而達到特定平臺支持的,官方文檔中也全方位介紹了在 Android 和 iOS 平臺的具體實現方法及例子,Web 平臺的實現卻介紹的較少。總結起來,Web 平臺和 Native 平臺實現方式的不一樣主要集中在下面兩點。

首先,Web Plugin 推薦的方式不是以其平臺特有的 JS 語言實現,而是經過 Dart Library 或 Package 實現,對於已有現成可用的 JS SDK 或須要大量使用 JS 實現功能的狀況下,官方提供了 package:js 包調用 Javascript,從而實現與 Javascript 的交互。

其次,Web Plugin 不是經過註冊 MethodChannel 傳遞消息的,Flutter 內部可直接調用經過官方指定形式 (Federated Plugin) 編寫的 Flutter Web Plugin 類。

下圖完整的展現了一個 Plugin 的總體架構:

Flutter Plugin 架構圖

4.1.2 基礎依賴建設

總體來說,MTFlutter 基礎依賴都是使用 Plugin 的形式開發維護的。爲處理依賴中的公共邏輯,提升 Plugin 的可擴展性,MTFlutter Plugin 在 Flutter Plugin 架構(各平臺原生實現層和 Plugin Interface 層)之上又增長了公共邏輯處理層,最終暴露給用戶是 Plugin API 層提供的接口。MTFlutter Plugin 架構圖以下:

MTFlutter Plugin 架構圖

在細節實現上,因爲項目中各類依賴的類型之間存在着差別,所以在依賴處理上也略有不一樣,下面介紹擁有不一樣特色的依賴所對應解決方案。

(1)各平臺實現能在 Web 側對齊的場景,如埋點庫

埋點庫不管在 Native 端仍是在 Web 端都是使用公司統一提供的 SDK,在 API 設計上具備自然的一致性,所以咱們徹底有能力在 Plugin Interface 層對齊全部接口,上層業務邏輯只需按需作些兼容處理便可。埋點庫 Web 端擴展的總體設計思路以下:

  1. 在業務項目的 web/index.html 文件中直接引入 Script 腳本而且進行初始化 (注意:引入 Script 的位置,須要放在 main.dart.js 前面)。
  2. 藉助 package:js 庫調用埋點 JS SDK,對齊 Flutter 埋點庫的 API ,實現 Flutter Plugin 的 Web 端支持,詳細架構圖以下圖所示:

埋點庫架構圖

(2)各平臺實如今 Web 側沒法對齊的場景,如路由庫

MTFlutter 路由庫是 Native 底層維護的一套全新的路由體系,依靠原生支持提供了強大的定製化功能,而在 Web 端沒法這些沒法在各平臺原生實現層達到 100% 支持。因爲 MTFlutter Plugin 最終暴露的是 Plugin API,所以咱們選擇直接對齊 Plugin API 實現路由庫在 Web 端的支持(藉助 Flutter Navigator、dart:html 用純 Dart 語言完成了擴展),詳細架構以下圖所示:

路由庫架構圖

(3)Web 端須要經過大量 JS 實現功能的依賴庫,如請求庫

因爲在現有的 Web 請求中統一封裝着大量的業務處理邏輯(如攔截器、異常上報等),若是用 Dart 從新實現一遍,成本仍是較高的。想複用原有基於 Axios( JS 請求庫) 封裝的請求庫就至關於讓 Plugin 的 Web 平臺實現使用 JS 語言。Dart 和 JS 交互是經過 package:js 進行接口調用,所以咱們在公共邏輯處理層用 Dart 對齊了相應的 API,詳細架構圖以下圖所示:

請求庫架構圖

4.2 性能優化

常規的 Web 項目中,爲了保證頁面有更好的加載和渲染性能,在靜態資源文件的處理方面,咱們須要作不少的工做,例如:資源文件 Hash 化、CDN 化、按需加載處理等,這些能夠經過 Webpack、Rollup 等構建工具進行預處理。但在 Flutter Web 中,這些預處理的操做目前官方還不支持,緣由是 Flutter 暴露給咱們的命令只有一個 flutter build web,致使咱們沒法直接進行更細粒度的個性化定製。若是想要讓 Flutter Web 達到企業級應用的標準,咱們須要更深層次的探索 Flutter SDK 的運行原理。下面咱們列出目前遇到的性能問題及其解決方案。

4.2.1 目前存在的性能問題

Google 官方對 Flutter Web 性能優化所作的事項還比較少,編譯輸出的頁面存在較大的性能問題,主要體如今如下兩方面:

  1. 首屏渲染時間長。即便使用了 FutureBuilder 把業務代碼拆分紅 xxx.part.js 以後,main.dart.js 體積依然維持在 1.1M。單一文件加載、解析時間過長,且靜態資源缺乏 CDN 化的支持,勢必會影響首屏的渲染時間。
  2. 滾動性能較差。 Flutter Web 自身實現了一套頁面滾動機制,在頁面滾動過程當中,會頻繁的建立 Canvas,最終致使滾動性能問題,甚至引發頁面 Crash。

經過下圖對瀏覽器網絡監控狀況的展現,能夠清晰的反映出以上問題:

瀏覽器網絡監控

頁面滾動過程當中,內存的佔用狀況

爲了解決上述的性能問題,咱們探索了 Flutter SDK 編譯過程,總結出從 Flutter 業務代碼到 Web 產物的總體流程,詳細流程以下圖所示:

編譯流程

從流程中咱們能夠看到,Flutter 在 Web 端目前只支持 Dart-->JS 的轉換,以及 UI 層的對齊,在工程化和性能優化方面作的工做並很少。

所以,咱們必須解決以上的性能問題,才能保證咱們的業務能夠正常的交付。經過對編譯流程的仔細分析與梳理,咱們在 AOT 產物生成以前對 Flutter SDK 進行定製,分別進行加載性能優化內存性能優化,下面分別介紹這兩部分的內容。

Flutter SDK 進行定製後的流程

4.2.2 加載性能優化

運行 flutter build web 命令以後,咱們獲得的主要靜態資源有:主文件 main.dart.js(1.1M),各頁面的業務代碼 xxx.part.js(使用 FutureBuilder 後)、圖片文件。直接應用這些資源到項目中,會遇到如下問題:

  1. 功能沒法及時更新:瀏覽器對同名文件的緩存,可能致使程序代碼不被及時更新或者出現執行錯亂。
  2. 首屏渲染性能差:main.dart.js 文件過大,單一文件加載、解析時間過長,勢必會影響首屏的渲染時間。
  3. 沒法使用 CDN:Flutter 僅支持相對路徑的加載方式,沒法使用當前域名之外的 CDN 域名,致使沒法享受 CDN 帶來的優點。

爲此,在加載部分咱們對 Flutter SDK 增長了以下三方面的優化,以達到線上運行的標準,優化步驟以下圖所示:

優化步驟

資源文件 Hash 化

除了 web/index.html 文件以外,咱們要對全部的引用到文件進行 Hash 化。對 build_system/web.dart 的修改按如下步驟進行:

  1. 遍歷產物目錄,並創建 ResourceMap。
  2. 分別計算每一個文件的 Hash 值。
  3. 爲新文件命名爲 name-[hash].xxx。
  4. 修改新文件名在對應文件中的引用關係。

大文件分片

Flutter Web 編譯以後會生成 main.dart.js 這一主文件,體積爲1.1M( Gzip 以後約 400K ),這給頁面的加載性能帶來很大的影響。爲此,咱們對代碼進行分片,藉助瀏覽器對多文件並行加載的特性,能夠有效提高頁面的加載性能。

具體實施步驟是:將 main.dart.js 在 Dart 側拆分紅多份純文本文件,前端經過 XHR 的方式並行加載並按順序拼接成 Javascript 代碼置於 <script> 標籤中,從而實現分片文件的並行加載。

Hash化以及分片以後,靜態資源的引用關係

資源文件 CDN 化

因爲 Flutter Web 資源引用機制的不一樣,即便在資源文件 Hash 化的過程當中,把文件的相對路徑替換成帶 CDN 域名的絕對路徑,也沒法實現 CDN 資源的加載。同時本地測試發現圖片和 Javascript 資源的加載邏輯還不盡相同,爲此針對各自的加載邏輯要分別進行優化。

  • 圖片處理:通過對源碼的大量閱讀及梳理,咱們發現圖片請求的 URL 首先會讀取 meta 標籤中 assetBase 值進行 URL 路徑拼接,根據拼接好的 URL 來獲取資源。目前,在項目 web/index.html 模板文件中並無 meta 標籤,因而就會根據相對路徑進行請求。解決方案是在編譯過程當中,根據請求環境增長 meta 標籤並把 content 設置爲 CDN 路徑。
  • JavaScript 處理:爲了解決圖片資源文件的加載問題,咱們雖然增長了 assetBasemeta 標籤,但發現 xxx.part.js 文件依然使用當前域名進行加載,可見 Javascript 資源的加載和圖片資源加載的邏輯不盡相同。對 main.dart.js 源碼分析,咱們發現請求 xxx.part.js 的域名取決於包含 main.dart.js 內容的 Script 標籤的 src 屬性。經過對 js_helper.dart 的動態編譯,咱們把讀取 src 屬性修改成讀取 window.assetBase 這一全局變量(meta 標籤中 assetBase 值加工後的變量)來實現 xxx.part.js 文件的 CDN 加載。

4.2.3 滾動性能優化

當頁面出現可滾動區域時,每次頁面滾動會建立大量的 Canvas。使用 Safari 的 Canvas 分析工具,咱們發現問題的根本緣由是頁面滾動的過程當中,Flutter 會頻繁的建立滾動區域的 Canvas,每次建立的 Canvas 內存都在10~70M 不等,滾動的內容越多,內存的佔用就會越大,這樣滾動幾幀以後,內存的佔用就會超過瀏覽器的閾值。

Safari 圖形工具中展現 Canvas 的佔用

Flutter 對 Canvas 的管理有一個 ReusablePool 的概念,在初始過程當中會建立必定的數量的 Canvas,頁面交互過程當中沒有變化的部分,會優先使用 pool 中已經緩存過的 Canvas 以便可以節省內存。因爲 Flutter Web 自身實現了一套頁面滾動機制,頁面滾動過程當中,會頻繁計算位置信息,引發滾動區域內容被從新建立,這就是爲何每次滾動都會建立 Canvas 的緣由。

咱們設計的解決方案是:修改 FlutterSDK,在滾動的過程當中定義一個閾值,當滾動的高度在閾值範圍內,咱們就會把當前的 Canvas 緩存起來。這樣選擇性的建立和銷燬 Canvas 能夠有效的緩解內存壓力,從而提高頁面滾動性能。

優化以後瀏覽器建立和銷燬 Canvas 的過程

4.3 構建與部署

4.3.1 Docker 鏡像定製

因爲 MTFlutter Web 環境安裝步驟較固定,且整個安裝過程耗時較長 ( > 80s ) 。所以將其定製爲 Docker 鏡像並集成至 Talos,Flutter Web 編譯階段便能免去安裝流程,有效提高構建效率。Docker 鏡像定製和發佈的詳細流程見官方文檔,本文再也不贅述。其中用於定製 Flutter Web 鏡像的 Dockerfile 文件以下:

FROM $BaseImage \# 繼承基礎鏡像
RUN apt-get update
RUN apt-get install rubygems -y
RUN gem install flutter-cli
RUN flutter-cli install
ENV PATH="/$User/.flutter_sdk/bin:${PATH}"
ENV PUB\_HOSTED\_URL="https://xxx.com" \# 私有pub服務
ENV FLUTTER\_STORAGE\_BASE_URL="https://storage.flutter-io.cn"
RUN ~/.flutter_sdk/bin/flutter config --enable-web

4.3.2 持續交付與部署

爲了實現持續交付與部署,咱們創建起了 Flutter Web 在 Talos(美團內部前端持續交付解決方案) 中的發佈流水線:

Talos 發佈流水線

能夠看到,流水線中已經免去了 MTFlutter Web 環境的安裝流程,現有流水線中重要節點介紹以下:

  • Flutter-Web-Build 利用 Docker 內置的 MTFlutter 進行 Web 編譯。
  • Flutter-Web-Publish 負責將編譯產物上傳美團資源存儲服務器。

5、成果展現

5.1 效果展現

咱們在美團外賣商家學院(一個以文章、視頻等形式幫助商家學習外賣運營知識、瞭解行業發展和平臺策略的平臺,它有很強的傳播屬性,具備外部投放的場景)率先落地了 Flutter Web,現以商家學院視頻內容頁爲例,對比 Flutter Native 和 Flutter Web 的展示效果:

Flutter Native

Flutter Web

能夠看出,二者的交互、視覺體驗是高度一致的,既保證了業務在 App 內接近 Native 的體驗,又極大提升了 Web 與 Flutter Native 的體驗一致性。

5.2 頁面加載性能

如前文所述,咱們實施了一系列針對 Flutter Web 的資源優化手段,使得頁面加載性能有較大提高,其中頁面徹底加載時間大體由 1300ms (TP50) 降到了 580ms(TP50),更多的性能指標數據見下圖:

某7日性能趨勢圖

能夠看到 Flutter Web 與現有 Web 項目性能指標數據差距已不大,可知足平常業務要求。但加載性能數據仍有較大的優化空間,咱們會持續對其進行探索。

5.3 滾動性能

針對滾動優化,咱們經過修改 Flutter SDK,使得 Canvas 在頁面滾動時無需重複建立,而是被緩存起來。這樣大大節省了內存的開銷(優化後頁面內存佔用穩定爲 100M 左右,與常規 Web 頁面無異),同時在必定程度上提高了滾動性能。以商家學院文章內容頁爲例,對比優化先後滾動 FPS :

優化前 FPS

優化後 FPS

能夠看到,Flutter Web 頁面滾動性能已獲得較大提高,足以應對大部分業務場景。但因爲 Flutter Web 頁面滾動過程當中會頻繁進行位置信息的計算,在複雜的業務場景(如頁面存在大量動畫) 仍然會暴露出必定的問題。所以對滾動性能的進一步優化也會是咱們將來的工做重心。

5.4 業務迭代效率

基於團隊對 Flutter Web 工程化能力的建設和 Flutter 良好的跨平臺特性,Flutter Web 在美團外賣商家學院改版需求的落地,大大提高了迭代效率,估算人效提高 40% 以上,計算公式爲:

其中 E 表明人效提高,Ci 指的是兼容和適配所耗費的時間,Np 表示業務跨端數量,目前美團外賣商家學院在 Native 和 H5 兩端完成了複用,後續在 PC 側需求的對齊中,效率提高數值會被放大,預計人效提高達 60% 以上。同時咱們將在更多的業務中進行推廣與應用,提高總體業務的迭代效率。

6、總結與展望

綜上所述,美團外賣商家端多元的業務形態和足夠的技術「儲備」,使得基於 Flutter 實現多端複用成爲了可能。而 Flutter Web 在美團外賣商家學院業務中也取得了階段性的成果,實現了 App、H5 側的體驗一致性,爲後續推進更多業務線實現 App-Web 一體化打下了堅實的基礎。

能夠預見的是,基於 Flutter Web 實現的多端複用,勢必會有效縮短項目交付週期。但因爲咱們對頁面加載性能、滾動性能作的仍不夠完美,不足以應對更加複雜的業務場景,所以咱們依然還有許多工做:

  • 頁面滾動性能優化: 因爲 Flutter 與 Web 的佈局差別,使得 dart:ui ( Web ) 也受 Flutter Native 的佈局約束,如何打破這樣的約束,是解決滾動性能問題的關鍵。
  • 頁面加載性能優化: 當前的頁面加載性能仍有較大優化空間,須要對 Flutter 進行編譯干預與優化(如按需分離 main.dart.js),減少資源包大小,有效提高頁面加載性能。
  • Flutter Web 基建:完善並優化開發、調試、編譯、構建、部署鏈路,使得新老項目能快速接入 Flutter Web。
  • Flutter Web 在 PC 側的複用:與 UED 團隊共同制訂 PC 與 App 適配規範,同時基於 Dart2js 和 dart:ui (Web)的強大能力,實現邏輯的抽象,完成組件、模塊的適配,達到提效最大化;
  • 跟進 Flutter 官方動向:Flutter 2.0 的發佈,穩定了對 Web 的支持,同時默認採用 Canvaskit 編譯模式,此模式下對頁面滾動性能有較大提高。但因爲 canvaskit.wasm 文件過於龐大(2.5M),下降了加載性能,所以目前仍不建議在 Web 側直接使用 Canvaskit。不過官方承諾會在 2021 年對性能進行總體優化,仍是值得期待的,咱們也將保持跟進和溝通。

咱們會持續基於 Flutter Web 作更多的探索和嘗試。若是您對 Flutter Web 也感興趣,歡迎你們在文末評論區留言或者給出建議,很是感謝。

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

前端 | 算法 | 後端 | 數據 | 安全 | 運維 | iOS | Android | 測試

| 在公衆號菜單欄對話框回覆【2020年貨】、【2019年貨】、【2018年貨】、【2017年貨】等關鍵詞,可查看美團技術團隊歷年技術文章合集。

版權聲明

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

相關文章
相關標籤/搜索