Flutter Web 應該是 Flutter 開發者里最不“受寵”的平臺了,但是其實 Flutter 和 Dart 團隊對于 Web 的投入一直沒有減少,這也和 Flutter 還有 Dart 的"出生"有關系,今天就借著 Dart 團隊的 ?mer A?acan 和 Martin Kustermann 在油管的訪談視頻來聊一聊 Flutter Web 這一路過來的變化。
其實在以前我們聊過很多次,Flutter 早期的項目代號是 “Sky” ,誕生于 Google 內部的 Chrome 團隊,早期定位其實是一個「前端項目」,本身是為了探索更優秀的 Web 渲染技術而存在,所以起初 Flutter 的創始人和整個團隊幾乎都是來自 Web :
而 Dart 最初的宏大愿景是在 Chrome 的 V8 引擎中內置一個專門的 Dart 虛擬機(VM) ,目的是為 Web 提供一個比 JavaScript 更結構化、性能更強的替代方案。
當然,眾所周知這個計劃最終涼了,而后 Dart 團隊將戰略重心轉向了 dart2js
編譯器,也就是將 Dart 代碼轉譯為 JavaScript,這也是一開始的 Flutter Web 打下來基礎。
所以 Flutter 選擇 Dart 其中一個原因不難理解,大家都是同一個團隊內的,并且 Dart 沒什么其他選擇,它也可以一心一意成為 Flutter 的形狀。
當然,另外一個原因則是 Flutter 當時希望有一種語言同時支持 JIT 和 AOT,并且支持 Hotload ,而 Dart 恰好具備這兩種編譯模式的基礎能力。
所以,對于 Dart 來說,它的演進路徑其實可以說是“一路坎坷”,從 “Dart VM in V8” 的失敗,再到 dart2js
的“茍活”,再到后面 Flutter Web 的落地,以及之后的 WasmGC 提案,再到現在的 dart2wasm
,其實 Dart 一直心系 Web 平臺,因為這是它曾經目標的延續。
而對于 Flutter 來說, dart2js
將 Flutter 轉譯為 JavaScript 的實現,讓 Flutter 在 Web 平臺變得“不那么一致”,特別是在處理復雜動畫和大量 UI 元素時會遇到性能瓶頸,此時的 Web 平臺的特殊性也成了 Flutter 的技術債務。
所以 WebAssembly (Wasm) 平臺最終成了 Flutter Web 的新目標,它能夠讓 Flutter 在 Web 平臺和其他平臺上渲染效果對齊,并且還能提供不錯的性能,只是 Wasm 的初始版本(CanvasKit)缺少垃圾回收(GC)機制,不適合像 Dart 這樣的托管語言:
Wasm MVP 只提供了一個單一的、連續的內存塊,Wasm 模塊必須手動管理這塊內存,包括對象的分配和釋放;并且沒有 GC 支持,也就是其他語言必須將它們自己的整個垃圾回收器實現打包進 Wasm 模塊,這不僅降低了效率,還極大地增加了代碼體積。
最經典的問題是,這會導致在一個進程中同時運行兩個 GC 的尷尬局面:一個是打包在 Wasm 模塊內的 GC,另一個是瀏覽器用于管理 JavaScript 對象的 GC 。
所以早期 CanvasKit 在 Flutter Web 里并沒有什么優勢,大家還是更愿意基于
dart2js
,這也導致了 Flutter Web 一直處于兩種模式的尷尬局面。
因此 ,Dart 和 Flutter 團隊開始加入 WasmGC 的推動與落地工作,其實這是一項吃力不討好的過程,Google 從一開始就深度參與了標準的制定,好處就是確保了這個標準能盡可能滿足 Dart 的需求。
當然,最終受惠的還有 Kotlin/Wasm ,JetBrains 也率先基于 WasmGC 開發了全新的 Kotlin/Wasm 編譯器驗證了相對應的可行性,也是早期的積極實踐者之一。
WasmGC 從根本上將 WebAssembly 從一個“Web 上的更好的 C++ 平臺”轉變為一個“通用的高級語言虛擬機”,而 WasmGC 提案也為 WebAssembly 引入了一系列新的功能:
- WasmGC 引入了新的堆類型,用于創建結構化數據(
struct
)和數組(array
),讓虛擬機能夠了解它們的內存布局,從而高效地訪問字段,減少復雜的地址計算 - WasmGC 為托管對象定義了一個類型系統,包含了子類型化(
sub
)的概念,也就是 Wasm 代碼能夠表示源語言中的類繼承關系,編譯器可以將語言的類型層次結構直接映射到 Wasm 的類型系統上 - WasmGC 引入了一套新指令,例如用于創建實例的
struct.new
和array.new
,用于字段訪問的struct.get
和struct.set
,以及用于類型轉換和檢查的指令,如ref.cast
(類型轉換)和br_on_cast
(帶類型檢查的分支)
編譯器不再是將語言的對象模型編譯到線性內存中,而是將語言的對象模型映射到 WasmGC 的對象模型上 ,而 WasmGC 相較于傳統的將 GC 捆綁到 Wasm 模塊中的方法,主要好處在于:
- 代碼體積的急劇減小,由于不再需要捆綁內存管理代碼(無論是
malloc
還是一個完整的 GC),最終的 Wasm 二進制文件可以小得多 - WasmGC 實現了 Wasm 和 JavaScript 之間真正細粒度的對象互操作,一個 WasmGC 對象可以持有對一個 JavaScript 對象的引用,反之也是,這讓宿主的 GC 能夠正確地追蹤和回收跨越這兩個環境的循環引用
- 由于虛擬機了解 Wasm 堆上的對象結構,瀏覽器開發者工具(如堆分析器)能夠檢查 Wasm 內存,并顯示有意義的對象類型和字段信息
所以,基于 WasmGC 推進的順路,Flutter 官方在 Flutter 3.10 終于對于 Web 的未來有了明確的定位:
“Flutter Web 是第一個圍繞 CanvasKit 和 WebAssembly 等新興 Web 技術進行架構設計的框架。”
Flutter 團隊表示,Flutter Web 的定位不是設計為通用 Web 的框架,類似的 Web 框架現在有很多,比如 Angular 和 React 等在這個領域表現就很出色,而 Flutter 應該是圍繞 CanvasKit 和 WebAssembly 等新技術進行架構設計的框架。
而對于 Dart,也從 Dart 3 開始,對于 Web 的支持將逐步演進為 WebAssembly 的 Dart native 的定位:
什么是 WebAssembly 的 Dart native 現在應該很好理解了吧?一直以來 Flutter 對于 WebAssembly 的支持都是:使用 Wasm 來處理CanvasKit 的 runtime,而 Dart 代碼會被編譯為 JS,而這對于 Dart 團隊來時,其實是一個「妥協」的過渡期,而基于 WasmGC, Dart 已經開始支持直接編譯為原生的 Wasm 代碼。
從 dart2js
切換到 dart2wasm
帶來了顯著的性能飛躍,例如 WasmGC 編譯的應用在幀渲染速度上平均提升了 2 倍,而在衡量卡頓情況的第 99 百分位幀上,性能提升高達 3 倍 ,另外 wasm 構建的 Flutter Web 應用還支持了使用多個線程場景,而 HTML renderer 也在 3.29 版本正式移除。
skwasm
支持通過一個專用的 Web Worker 在獨立線程上進行渲染,可以將部分渲染工作負載分流,利用多核 CPU 來減少卡頓并提高響應性,前提是服務器必須配置特定的 COOP 和 COEP HTTP 標頭,從而滿足SharedArrayBuffer
的安全要求 。
當然,WasmGC 目前還是存在瀏覽器兼容性問題,比如:
- Chromium (Chrome, Edge): 從 119 版本穩定支持 WasmGC
- Firefox: 盡管從 120 版本開始支持 WasmGC,但存在一些特定錯誤,某些情況可以會導致 Flutter 的
skwasm
渲染器無法正常工作 - Safari/WebKit: 同樣已經支持 WasmGC,但也存在阻礙 Flutter 渲染的錯誤,特別是 iOS 上的 WebKit 兼容問題
目前 WebKit 已經默認支持了 WasmGC ,但是歷史版本依然存在需要兼容的場景:
而回歸到 Flutter Web 上,就是 canvaskit
和 skwasm
有什么區別?簡單說:
canvaskit
使用dart2js
,兼容性更廣skwasm
使用dart2wasm
,性能更好體積更小,但是依賴 WasmGC 環境
所以,也許 Flutter Web 在開發者領域并不是很受寵,但是 Flutter 和 Dart 對它的投入并不少,因為 Wasm 的潛力已經遠遠超出了瀏覽器的范疇 ,例如 WASI(WebAssembly System Interface)就是未來的重要趨勢之一,作為新興的標準,它的目的是為 Wasm 模塊提供一種安全、可移植的方式來與系統資源(如文件系統)進行交互 。
WASI 的目標是定義一套標準的 API,讓 Wasm 代碼可以“一次編譯,在任何(支持 WASI 的)運行時上運行”,無論是服務器、邊緣設備還是桌面應用。
例如:
- WASI 可以讓 WebAssembly 成為 Node.js 和 Python 等傳統服務器運行時的高性能替代品
- 借助 WASI,Wasm 模塊將能夠和傳感器、本地文件系統交互來處理邊緣設備上的數據,從而實現邊緣的實時決策
- WASI 的系統級功能能讓 WebAssembly 在傳統容器領域成為輕量級替代品
而 dart2wasm
編譯器目前是主要針對瀏覽器中的 JS 環境,但未來它也許可以被擴展以支持非 JS 的、符合 WASI 標準的運行時,例如 Wasmtime 或 Wasmer ,從而支持更多場景,這也是為什么 Flutter 和 Dart 對于 Wasm 持續投資的原因之一。
這也是訪談里 A?acan 和 Kustermann 未來愿景:在一個通用的、高性能的、可移植的運行時的驅動下,原生應用和 Web 應用之間的界限會更加模糊,盡管挑戰依然存在,但其發展軌跡已經為一類全新的 Web 體驗設定了方向。
而回歸的目前的 Flutter Web 和當前推進的情況,未來大概會有:
- Flutter Web Hotload 的穩定版發布
- 基于 Flutter Web 的 IDE 內 Widget Preview 穩定版
- 基于語義樹實現 SEO 優化,例如 #145954 就提到過,通過將無障礙的
Semantics Tree
翻譯成 HTML 結構的管線,讓 Web 可以滿足搜索引擎爬蟲讀取的需求
雖然 Flutter Web 現在還不夠好用,不夠通用,但是它也確確實實撐起了 Flutter 在 Web 平臺的能力,對比起剛剛才合并到 master 的桌面端多窗口的情況,其實 Web 的投入在每個版本都有目共睹,未來 Flutter Web 應該也不會往通用領域發展,但是在 WebAssembly 領域,Flutter 和 Dart 應該還是可以有一席之地。
參考鏈接
- https://www.youtube.com/watch?v=vgOABOvtBT8