【文章內容使用 Gemini 2.0 自動翻譯產生】
Dart 中 JS 互通性的歷史
由於 Dart 3.3 中實現了一個令人興奮的 JavaScript 互通性里程碑,目前 Flutter beta 版已支援 Wasm。為了慶祝,我們將回顧 Dart 和 JS 互通性十年來的旅程。
從 Dart 誕生之初,互通性就一直是核心重點。Dart 於 2011 年首次發佈時,其設計目標是可嵌入且跨平台。它可以在獨立虛擬機器上運行、嵌入瀏覽器中,並編譯成 JavaScript。2015 年 Flutter 出現時,我們也準備將其嵌入其中。現在,我們也很高興能以 WasmGC 執行階段為目標。
起初,我們迅速地公開了 Dart 嵌入的每個平台的功能。這就是我們的 SDK 平台特定函式庫的起源:dart:io 公開了 VM 上的檔案系統,dart:html 公開了 Web 上的瀏覽器 API,等等。這些函式庫的外觀和感覺就像普通的 Dart 函式庫,但在幕後隱藏了一些複雜的低階原生基元,以使其運作。這是我們發明的第一種互通性形式。它具有表現力,但僅限於 SDK 函式庫。
在 Web 上,開發人員需要的東西不僅僅是瀏覽器 API。因此,我們開始尋找方法來開放互通性以涵蓋更多目標。作為起點,我們在 2013 年引入了 dart:js,以便能夠存取 JavaScript 函式庫。
1 | // 簡短的 JavaScript 程式碼範例,用於說明 Dart/JS 互通性 |
1 | // 透過 `dart:js` 存取 (2013) |
那時我們就知道 dart:js 不是我們想要的程式設計模型。您必須使用字串來存取 JavaScript 中的名稱——別想在編譯時發現問題,更別想程式碼完成了!實作的成本也很高。它嚴重依賴於大多數操作的 boxes 和深層副本。因此,我們在 2014 年和 2015 年繼續起草想法,直到 package:js v0.6 發佈。
1 | // 透過 `package:js` 存取 (2015) |
有了 package:js,我們終於有了一個高效且使用者友好的開放 API。您可以在抽象類別上添加一些註釋,然後瞧,您就可以存取 JavaScript API 了。一切都像魔法一樣運作,直到它失效為止。有很多事情您無法使用 package:js 完成:直接存取瀏覽器 API、重新命名成員、轉換、附加 Dart 邏輯等等更多。為了彌補,我們還發佈了 dart:js_util——一個類似於 dart:js 的輕量級且高效的低階 API,作為備份。package:js 中的所有限制都讓我們很困擾,但我們束手無策。我們需要 Dart 語言提供更多功能才能做得更好。
大約在那個時候,我們已經在著手進行我們對該語言做過最大的變更——我們正在讓 Dart 變得可靠。諷刺的是,當我們在 2018 年與 Dart 2.0 一起發佈新的類型系統時,互通性變得更糟了!除了早期的限制之外,package:js 的特殊之處的魔法也有其黑暗面——它無法檢查類型的有效性。這意味著我們的互通性在我們原本可靠的語言中是不穩定的來源。
然後,我們的旅程轉變為專注於協同改進 Dart 和 JS 互通性。憑藉明確的原則(慣用、表達、組合、精確、易於理解、務實、非魔幻且完整),我們朝著一個基於類型和靜態分派的設計方向發展,並挑戰了 Dart 語言。接下來是並行演變。
- 2019 年,Dart 2.7 加入了靜態擴充方法。您可以將自訂 Dart 邏輯附加到 JS 互通類別,並轉換值,例如將 JS Promise 轉換為 Dart Future,而無需使用包裝器。
- 2021 年,我們與 package:js v0.6.4 一起發佈了 @staticInterop。終於,JS 互通性具有足夠的表現力——您可以公開以前由 SDK 函式庫(如 dart:html)獨家管理的瀏覽器 API。
- 2023 年,當我們在 Dart 3.0 中放棄了不穩定的空安全時,我們終於可以看到我們取得的進展,我們的設計和 @staticInterop 的工作清楚地表明我們準備好解決我們長期以來存在的可靠性差距。
那一年,我們引入了編譯到 WasmGC,並利用 JS 互通性在其上運行豐富的框架,例如 Flutter Web。這激發了對 JS 類型 的研究,以在程式設計模型中明確定義 Dart 和 JS 的邊界,並找到一種在 Wasm 和 JS 編譯目標中使用 JS 的一致方法。我們還開始了 擴充類型 語言實驗——Dart 3.3 中推出的一項功能,彌合了 Dart 語言和 JS 互通性之間的差距。多年來,JS 互通性具有一些行為,例如類型擦除,這些行為與 Dart 中的其他任何東西都不匹配。有了擴充類型,JS 互通性終於可以變得慣用,並在 Dart 開發工具中獲得應有的支援。
儘管一路走來經歷了許多變化和轉折,但有一件事在整個十年中始終如一:Dart 社群的積極參與。社群成員率先測試和貢獻 dart:js,後來又影響了 package:js 的設計。他們編寫工具來解決功能差距 (package:js_wrapping),並嘗試透過自動生成 Dart API (package:js_facade_gen,package:js_bindings,package:typings) 來提高生產力。每一項貢獻都使 Dart 的互通性設計變得更好。感謝你們每一個人,讓這成為一次如此激動人心的冒險!
最後,我們來到了 2024 年。我們在 Dart 3.3 中與 package:web 一起發佈了 dart:js_interop,這是 Dart 中最新的 JS 互通性解決方案,使 將 Flutter 編譯為 Wasm 成為可能。
1 | // 透過 `dart:js_interop` 存取 (2024) |
- dart:js_interop 是一種基於擴充類型的靜態、可靠、慣用、表達且一致的互通性形式,能夠公開任何 JavaScript 或瀏覽器 API。
- package:web 使用 dart:js_interop 來完成 13 年前 dart:html 所做的事情,但以 JavaScript 和 WasmGC 都支援的方式進行。
今天,我們很高興慶祝 Dart/JS 互通性的一種新形式及其所帶來的未來。了解我們的過去,我們確信這不是旅程的終點,而是我們歷史上一個激動人心的時刻。
我們迫不及待地想看到您用它構建出什麼!
Dart 中 JS 互通性的歷史 最初發佈在 Dart 上的 Medium,人們在那裡透過突出顯示和回應這個故事來繼續討論。