- 開始日期:2018-02-14
- RFC PR:(保留為空白)
- 追蹤問題:(保留為空白)
摘要
讓 Rust 箱子能透明地依賴於 npm 生態系統中的套件。透過 Cargo 的正常 Rust 依賴關係和由其他箱子使用時,這些依賴關係將能順利運作。
動機
wasm-bindgen
和 wasm-pack
的主要目標是能讓 Rust 與 JS 無縫整合。JS 生態系統中龐大的一部份目前在 wasm-bindgen
和 wasm-pack
中僅有少量支援,這讓開發人員難以存取 JS 所提供的豐富資源!
此 RFC 的目標是讓這些依賴關係能夠存在。Rust 箱子應能像 NPM 可要求編譯成 wasm 的 Rust 箱子一樣,要求 NPM 的功能。任何目前使用 NPM 套件的工作流程(例如使用打包器打包 WebAssembly)應繼續運作,且允許在 Rust 依賴關係要求時拉進「自訂」NPM 套件。
利害關係人
此 RFC 主要影響到 wasm-pack
和 wasm-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-bindgen
和 wasm-pack
CLI 工具有輸出模式(特別是 wasm-bindgen
的 --no-modules
和 wasm-pack
的 --target no-modules
旗標),旨在無需 NPM 和其他 JS 工具。在這種情況下,如果偵測到任何 Rust 板條箱中的 package.json
,系統將會發出一個錯誤訊息,表示如此。
請注意,這表示旨在搭配 --no-modules
運作的核心成品無法新增 NPM 相依性。它們必須從 crates.io 匯入 Rust 相依性,或使用類似 local JS snippets 的功能來匯入自訂 JS 程式碼。
缺點
此 RFC 的主要缺點之一在於它與 wasm-bindgen
和 wasm-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
中支援更多欄位?