• 開始日期:2018-02-14
  • RFC PR:(保留為空白)
  • 追蹤問題:(保留為空白)

摘要

讓 Rust 箱子能透明地依賴於 npm 生態系統中的套件。透過 Cargo 的正常 Rust 依賴關係和由其他箱子使用時,這些依賴關係將能順利運作。

動機

wasm-bindgenwasm-pack 的主要目標是能讓 Rust 與 JS 無縫整合。JS 生態系統中龐大的一部份目前在 wasm-bindgenwasm-pack 中僅有少量支援,這讓開發人員難以存取 JS 所提供的豐富資源!

此 RFC 的目標是讓這些依賴關係能夠存在。Rust 箱子應能像 NPM 可要求編譯成 wasm 的 Rust 箱子一樣,要求 NPM 的功能。任何目前使用 NPM 套件的工作流程(例如使用打包器打包 WebAssembly)應繼續運作,且允許在 Rust 依賴關係要求時拉進「自訂」NPM 套件。

利害關係人

此 RFC 主要影響到 wasm-packwasm-bindgen 的使用者,而他們也同時使用像 Webpack 之類的打包器。然而,此 RFC 也會影響 Rust 生態系統中核心基礎箱子的開發人員,這些開發人員希望在存取 NPM 依賴關係時能有意識。

詳盡說明

將 NPM 依賴關係新增到 Rust 專案看起來會很類似於在 NPM 正常專案中新增 NPM 依賴關係。首先需要宣告依賴關係及其版本需求。此 RFC 提出在 Cargo.toml 檔旁的新增 package.json 檔中執行此動作。

 {
  "dependencies": {
    "foo": "^1.0.1"
  }
}

package.json 檔最初會是 NPM 的 package.json 檔的子集合,僅支援一個最高層級的 dependencies 金鑰,其內部具有包含字串的金鑰/值配對。在驗證此驗證之外,將不會對 dependencies 中的金鑰或值配對執行任何驗證。未來預定會支援 NPM 中 package.json 的更多金鑰,但此 RFC 打算先針對 NPM 的依賴關係提供 MVP。

在建立此 package.json 檔之後,接著需要在 Rust 箱子中輸入套件。這將與其他 Rust 對 JS 的依賴關係一樣,使用 #[wasm_bindgen] 屬性執行。


# #![allow(unused_variables)]
#fn main() {
#[wasm_bindgen(module = "foo")]
extern "C" {
    fn function_in_foo_package();
}
#}

備註:在 JS 中,以上的輸入將類似於

import { function_in_foo_package } from "foo";

#[wasm_bindgen] 屬性中已存在的 module 鍵可指示匯入源自哪一個 ES 模組。這會影響最終輸出的 wasm 二進位檔中 module 的鍵,並對應至 package.json 中套件的名稱。這旨在配合套件管理程式的慣例,將 NPM 套件解讀成 ES 模組。

這兩個工具就位後,你只需要執行 wasm-pack build,就大功告成了!最終的 package.json 會列出我們上面 package.json 中的 foo 相依性,並準備好通過套件管理程式使用。

技術實作

幕後有幾個活動的組成部分促成這一切的發生。讓我們先來看看 wasm-bindgen 中的各個部分。

這份 RFC 的首要目標是針對 NPM 啟用透明傳遞性的相依性。#[wasm_bindgen] 巨集是板條箱建置中唯一有存取所有傳遞性相依性的部分,所以我們會使用它來吸入 package.json。當指定含有 module 鍵的 #[wasm_bindgen] 時,它會在程序化巨集的 cwd 內尋找 package.json(請注意,cwd 由 Cargo 設定為編譯中的板條箱 Cargo.toml 或撰寫 #[wasm_bindgen] 的板條箱的目錄)。如果找到這個 package.json,它的絕對路徑會編碼進 wasm-bindgen 已經發出的自訂區段內。

稍後,當 wasm-bindgen CLI 工具執行時,它將會剖析並詮釋 wasm-bindgen 自訂區段中的所有項目。所有列出的 package.json 檔案都會被載入、剖析和驗證(也就是說,目前只允許 dependencies)。如果載入任何 package.json,那麼 package.json 檔案將會發布至 --out-dir 內輸出的 JS 檔案旁邊。

wasm-bindgen 執行後,wasm-pack 會讀取 package.json 輸出(如果有的话),並在其中加入已經發出的 metadata 和其他項目。

如果相依圖表中有多個板條箱相依於某個 NPM 套件,那麼這個 MVP 提議會產生錯誤。將來我們可以實作一定程度的版本需求合併,但目前為求簡潔,wasm-bindgen 會發出一個錯誤。

--no-modules 的互動

相依於 NPM 套件基本上需要,嗯,NPM,不論透過哪種方式。wasm-bindgenwasm-pack CLI 工具有輸出模式(特別是 wasm-bindgen--no-moduleswasm-pack--target no-modules 旗標),旨在無需 NPM 和其他 JS 工具。在這種情況下,如果偵測到任何 Rust 板條箱中的 package.json,系統將會發出一個錯誤訊息,表示如此。

請注意,這表示旨在搭配 --no-modules 運作的核心成品無法新增 NPM 相依性。它們必須從 crates.io 匯入 Rust 相依性,或使用類似 local JS snippets 的功能來匯入自訂 JS 程式碼。

缺點

此 RFC 的主要缺點之一在於它與 wasm-bindgenwasm-pack 的主要使用案例,即 --no-modules--target no-modules 旗標,基本不相容。作為一個短期的臨時解決方案,此 RFC 建議將其設為嚴重的錯誤,而這將阻礙希望在該模式中使用此功能的成品的採用。

但是,從長遠來看,使其發揮作用可能是可行的。例如,許多 NPM 套件在 unpkg.com 或其他位置上可用。如果所有這些位置中的套件都遵守已知的慣例,則可能可以產生與這些 NPM 套件託管位置相容的程式碼。在這些情況下,可能可以「只在幾個位置插入一個腳本標籤」,讓 --no-modules 能與 NPM 套件一起使用。不過,這是否可行仍不清楚。

基本原理和替代方案

在開發此 RFC 時,已說明了其設計的一些指導價值觀

  • 在 Rust 生成的 WebAssembly 專案上開發,應讓開發人員可以使用他們最習慣的開發環境。編寫 Rust 的開發人員應能夠使用 Rust,而使用 JavaScript 的開發人員應能夠使用基於 JS 的執行時期環境(例如 Node.js、Chakra 等)。

  • JavaScript 工具和工作流程應能與 Rust 生成的 WebAssembly 專案一起使用。例如,WebPack 和 Parcel 等打包器,或 npm audit 和 GreenKeeper 等相依性管理工具。

  • 在可能的範圍內,應做出讓解決方案對不只是 Rust,還有 C 和 C++ 開發人員可用的決定。

  • 應專注於建立工作流程,讓開發人員得以輕鬆學習並獲得高效率的開發體驗。

這些原則導向了上述使用 package.json 來宣告 NPM 相依性,接著再由 wasm-bindgen 將其歸成一組,由 wasm-pack 發佈的提案。透過使用 package.json,我們即可與現有的工作流程(例如 GreenKeeper 和 npm install)相容。此外,package.json 在 JS 生態系中已獲得非常良好的說明和支援,因此非常熟悉。

已排除的其他一些此 RFC 的替代方案如下

  • 使用 Cargo.toml,而非 package.json 來宣告 NPM 相依性。例如,我們可以用

    [package.metadata.npm.dependencies]
    foo = "0.1"
    

    不過它具有與所有圍繞著 package.json 的現有工作流程不相容的缺點。此外,它還突顯了 NPM 和 Cargo 的差異,以及如何解釋版本需求的 "0.1"(例如 ^0.1~0.1)。

  • 新增一個獨立的清單檔取代使用 package.json 也是一種可能性,而且可能比較好讓 wasm-bindgen 讀取和後續解析/包含。這麼做可能的好處是嚴格適用於我們的用例範疇,而且在禁止 package.json 中其他有效欄位時不會產生誤導。不過,這種方法的缺點與 Cargo.toml 相同,對大多數人而言都是陌生的格式,且與現有工具不相容,不會帶來太多好處。

  • 於內文中註解版本相依性也可以用於取代 package.json,例如

    
    # #![allow(unused_variables)]
    #fn main() {
    #[wasm_bindgen(module = "foo", version = "0.1")]
    extern "C" {
        // ...
    }
    #}

    與所有其他替代方案一樣,這與現有工具不相容,但它也不符合 Rust 自身宣告相依性的機制,因為這個機制會將版本資訊和程式碼本身分開。

未解決的問題

  • 僅使用 dependencies 的 MVP 限制是否太受限?是否應該在 package.json 中支援更多欄位?