這篇文章主要介紹JavaScript和Rust中wasm-bindgen組件有什么用,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
在網(wǎng)站設(shè)計制作、成都網(wǎng)站制作過程中,需要針對客戶的行業(yè)特點、產(chǎn)品特性、目標受眾和市場情況進行定位分析,以確定網(wǎng)站的風格、色彩、版式、交互等方面的設(shè)計方向。創(chuàng)新互聯(lián)建站還需要根據(jù)客戶的需求進行功能模塊的開發(fā)和設(shè)計,包括內(nèi)容管理、前臺展示、用戶權(quán)限管理、數(shù)據(jù)統(tǒng)計和安全保護等功能。
WebAssembly標準只定義了四種類型:兩種整數(shù)類型和兩種浮點類型。然而,大多數(shù)情況下,JS和Rust開發(fā)人員正在使用更豐富的類型! 例如,JS開發(fā)人員經(jīng)常與互以添加或修改HTML節(jié)點相關(guān)的文檔交互,而Rust開發(fā)人員使用類似Result等類型進行錯誤處理,幾乎所有程序員都使用字符串。
被局限在僅使用由WebAssembly所提供的類型將會受到太多的限制,這就是wasm-bindgen出現(xiàn)的原因。
wasm-bindgen的目標是提供一個JS和Rust類型之間的橋接。它允許JS使用字符串調(diào)用Rust API,或Rust函數(shù)捕獲JS異常。
wasm-bindgen抹平了WebAssembly和JavaScript之間的阻抗失配,確保JavaScript可以高效地調(diào)用WebAssembly函數(shù),并且無需boilerplate,同時WebAssembly可以對JavaScript函數(shù)執(zhí)行相同的操作。
wasm-bindgen項目在其README文件中有更多描述。要入門,讓我們深入到一個使用wasm-bindgen的例子中,然后探索它還有提供了什么。
1、Hello World!
學習新工具的最好也是最經(jīng)典的方法之一就是探索下用它來輸出“Hello, World!”。在這里,我們將探索一個這樣的例子——在頁面里彈出“Hello World!”提醒框。
這里的目標很簡單,我們想要定義一個Rust的函數(shù),給定一個名字,它會在頁面上創(chuàng)建一個對話框,上面寫著Hello,$name!在JavaScript中,我們可以將這個函數(shù)定義為:
代碼
export function greet(name) { alert(`Hello, ${name}!`); }
不過在這個例子里要注意的是,我們將把它用Rust編寫。這里已經(jīng)發(fā)生了很多我們必須要處理的事情:
JavaScript將會調(diào)用一個WebAssembly 模塊, 模塊名是 greetexport.
Rust函數(shù)將一個字符串作為輸入?yún)?shù),也就是我們要打招呼的名字。
在內(nèi)部Rust會生成一個新的字符串,也就是傳入的名字。
最后Rust會調(diào)用JavaScript的 alert函數(shù),以剛創(chuàng)建的字符串作為參數(shù)。
啟動第一步,我們創(chuàng)建一個新的Rust工程:
代碼
$ cargo new wasm-greet --lib
這將初始化一個新的wasm-greet文件夾,我們的工作都在這里面完成。接下來我們要使用如下信息修改我們的Cargo.toml(在Rust里相當于package.json):
代碼
[lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"
我們先忽略[lib]節(jié)的內(nèi)容,接下來的部分聲明了對wasm-bindgen的依賴。這里的依賴包含了我們使用wasm-bindgen需要的所有的支持包。
接下來,是時候編寫一些代碼了!我們使用下列內(nèi)容替換了自動創(chuàng)建的src/lib.rs:
代碼
#![feature(proc_macro, wasm_custom_section, wasm_import_module)] extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); }
如果你不熟悉Rust,這可能看起來有點啰嗦,但不要害怕!隨著時間的推移,wasm-bindgen項目不斷改進,而且可以肯定的是,所有這些并不總是必要的。
要注意的最重要的一點是#[wasm_bindgen]屬性,這是一個在Rust代碼中的注釋,這里的意思是“請在必要時用wrapper處理這個”。我們對alert函數(shù)的導入和greet函數(shù)的導出都被標注為這個屬性。稍后,我們將看到在引擎蓋下發(fā)生了什么。
首先,我們從在瀏覽器中打開作為例子來切入正題!我們先編譯wasm代碼:
代碼
$ rustup target add wasm32-unknown-unknown --toolchain nightly # only needed once $ cargo +nightly build --target wasm32-unknown-unknown
這段代碼會生成一個wasm文件,路徑為target/wasm32-unknown-unknown/debug/wasm_greet.wasm。如果我們使用工具如wasm2wat來看這個wasm文件里面的內(nèi)容,可能會有點嚇人。
結(jié)果發(fā)現(xiàn)這個wasm文件實際上還不能直接被JS調(diào)用!為了能讓我們使用,我們需要執(zhí)行一個或更多步驟:
代碼
$ cargo install wasm-bindgen-cli # only needed once $ wasm-bindgen target/wasm32-unknown-unknown/debug/wasm_greet.wasm --out-dir .
很多不可思議的事情發(fā)生都發(fā)生在這個步驟中:wasm-bindgen CLI工具對輸入的wasm文件做后期處理,使它變的“suitable”可用。
我們待會再來看“suitable”的意思,現(xiàn)在我們可以肯定的說,如果我們引入剛創(chuàng)建的wasm_greet.js文件(wasm-bindgen工具創(chuàng)建的),我們已經(jīng)獲取到了在Rust中定義的greet函數(shù)。
最終我們接下來要做的是使用bundler對其打包,然后創(chuàng)建一個HTML頁面運行我們的代碼。
在寫這篇文章的時候,只有Webpack's 4.0 release對WebAssembly的使用有足夠的支持(盡管暫時已經(jīng)有了 Chrome caveat)。
總有一天,更多的bundler也會接著支持WebAssmbly。在這我不再描述細節(jié),但是你可以看一下在Github倉庫里的example配置。不過如果我們看內(nèi)容,這個頁面中我們的JS在看起來是這樣的:
代碼
const rust = import("./wasm_greet"); rust.then(m => m.greet("World!"));
…就是這些了!現(xiàn)在打開我們的網(wǎng)頁就會顯示一個不錯的“Hello, World!”對話框,這就是Rust驅(qū)動的。
2、wasm-bindgen是如何工作的
唷,那是一個巨大的“Hello, World!”。讓我們深入了解一下更多的細節(jié),以了解后臺發(fā)生了什么以及該工具是如何工作的。
wasm-bindgen最重要的方面之一就是它的集成基本上是建立在一個概念之上的,即一個wasm模塊僅是另一種ES模塊。例如,在上述中我們想要一個帶有如下簽名的ES模塊(在Typescript中):
代碼
export function greet(s: string);
WebAssembly無法在本地執(zhí)行此操作(請記住,它目前只支持數(shù)字),所以我們依靠wasm-bindgen來填補空白。
在上述的最后一步中,當我們運行wasm-bindgen工具時,你會注意到wasm_greet.js文件與wasm_greet_bg.wasm文件一起出現(xiàn)。前者是我們想要的實際JS接口,執(zhí)行任何必要的處理以調(diào)用Rust。* _bg.wasm文件包含實際的實現(xiàn)和我們所有的編譯后的代碼。
我們可以通過引入 ./wasm_greet 模塊得到 Rust 代碼愿意暴露出來的東西。我們已經(jīng)看到了是如何集成的,可以繼續(xù)看看執(zhí)行的結(jié)果如何。首先是我們的示例:
代碼
const rust = import("./wasm_greet"); rust.then(m => m.greet("World!"));
我們在這里以異步的方式導入接口,等待導入完成(下載和編譯 wasm)。然后調(diào)用模塊的 greet 函數(shù)。
注: 這里用到的異步加載目前需要 Webpack 來實現(xiàn),但總會不需要的。而且,其它打包工具可能沒有此功能。
如果我們看看由 wasm-bindgen 工具為 wasm_greet.js 文件生成的內(nèi)容,會看到像這樣的代碼:
代碼
import * as wasm from './wasm_greet_bg'; // ... export function greet(arg0) { const [ptr0, len0] = passStringToWasm(arg0); try { const ret = wasm.greet(ptr0, len0); return ret; } finally { wasm.__wbindgen_free(ptr0, len0); } } export function __wbg_f_alert_alert_n(ptr0, len0) { // ... }
注: 記住這是生成的,未經(jīng)優(yōu)化的代碼,它可能既不優(yōu)雅也不簡潔??!在 Rust 中通過 LTO(Link Time Optimization,連接時優(yōu)化)創(chuàng)建新的發(fā)行版,再通過 JS 打包工具流程(壓縮)之后,可能會精簡一些。
現(xiàn)在可以了解如何使用wasm-bindgen來生成greet函數(shù)。在底層它仍然調(diào)用wasm的greet函數(shù),但是它是用一個指針和長度來調(diào)用的而不是用字符串。
了解passStringToWasm的更多細節(jié)可以訪問Lin Clark's previous post。它包含了所有的模板,對我們來說這是除了wasm-bindgen工具以外還需要去寫的東西!然后我們接下來看__wbg_f_alert_alert_n函數(shù)。
進入更深一層,下一個我們感興趣的就是WebAssmbly中的greet函數(shù)。為了了解這個,我們先來看Rust編譯器能訪問到的代碼。注意像上面生成的這種JS wrapper,在這里你不用寫greet的導出符號,#[wasm_bindgen]屬性會生成一個shim,由它來為你翻譯,命名如下:
代碼
pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } #[export_name = "greet"] pub extern fn __wasm_bindgen_generated_greet(arg0_ptr: *mut u8, arg0_len: usize) { let arg0 = unsafe { ::std::slice::from_raw_parts(arg0_ptr as *const u8, arg0_len) } let arg0 = unsafe { ::std::str::from_utf8_unchecked(arg0) }; greet(arg0); }
現(xiàn)在可以看到原始代碼,greet,也就是由#[wasm_bindgen]屬性插入的看起來有意思的函數(shù)__wasm_bindgen_generated_greet。這是一個導出函數(shù)(用#[export_name]和extern關(guān)鍵詞來指定的),參數(shù)為JS傳進來的指針/長度對。在函數(shù)中它會將這個指針/長度轉(zhuǎn)換為一個&str (Rust中的一個字符串),然后將它傳遞給我們定義的greet函數(shù)。
從另一個方面看,#[wasm_bindgen]屬性生成了兩個wrappers:一個是在JavaScript中將JS類型的轉(zhuǎn)換為wasm,另外一個是在Rust中接收wasm類型并將其轉(zhuǎn)為Rust類型。
現(xiàn)在我們來看wrappers的最后一塊,即alert函數(shù)。Rust中的greet函數(shù)使用標準format!宏來創(chuàng)建一個新的字符串然后傳給alert。回想當我們聲明alert方法的時候,我們是使用 #[wasm_bindgen]聲明的,現(xiàn)在我們看看在這個函數(shù)中暴露給rustc的內(nèi)容:
代碼
fn alert(s: &str) { #[wasm_import_module = "__wbindgen_placeholder__"] extern { fn __wbg_f_alert_alert_n(s_ptr: *const u8, s_len: usize); } unsafe { let s_ptr = s.as_ptr(); let s_len = s.len(); __wbg_f_alert_alert_n(s_ptr, s_len); } }
這并不是我們寫的,但是我們可以看看它是怎么變成這樣的。alert函數(shù)事實上是一個簡化的wrapper,它帶有Rust的 &str然后將它轉(zhuǎn)換為wasm類型(數(shù)字)。它調(diào)用了我們在上面看到過的比較有意思的函數(shù)__wbg_f_alert_alert_n,然而它奇怪的一點就是#[wasm_import_module]屬性。
在WebAssembly中所有導入的函數(shù)都有一個其存在的模塊,而且由于wasm-bindgen構(gòu)建在ES模塊之上,所以這也將被轉(zhuǎn)譯為ES模塊導入!
目前__wbindgen_placeholder__模塊實際上并不存在,但它表示該導入將被wasm-bindgen工具重寫,以從我們生成的JS文件中導入。
最后,對于最后一部分的疑惑,我們得到了我們所生成的JS文件,其中包含:
代碼
export function __wbg_f_alert_alert_n(ptr0, len0) { let arg0 = getStringFromWasm(ptr0, len0); alert(arg0) }
哇! 事實證明,這里隱藏著相當多的東西,我們從JS中的瀏覽器中的警告都有一個相對較長的知識鏈。不過,不要害怕,wasm-bindgen的核心是所有這些基礎(chǔ)設(shè)施都被隱藏了! 你只需要在隨便使用幾個#[wasm_bindgen]編寫Rust代碼即可。然后你的JS可以像使用另一個JS包或模塊一樣使用Rust了。
wasm-bindgen還能做什么
wasm-bindgen項目在這個領(lǐng)域內(nèi)志向遠大,我們在此不再詳細贅述。探索wasm-bindgen中的功能一個有效的方法就是探索示例目錄,這些示例涵蓋了從我們之前看到的Hello World! 到在Rust中對DOM節(jié)點的完全操作。
wasm-bindgen高級特性如下:
引入JS結(jié)構(gòu),函數(shù),對象等來在wasm中調(diào)用。你可以在一個結(jié)構(gòu)中調(diào)用JS方法,也可以訪問屬性,這給人一種Rust是“原生”的感覺,讓人覺得你曾經(jīng)寫過的Rust #[wasm_bindgen] annotations都可以連接了起來。
將Rust結(jié)構(gòu)和函數(shù)導出到JS。與只用JS使用數(shù)字類型來工作相比,你可以導出一個Rust結(jié)構(gòu)并在JS中轉(zhuǎn)換成一個類。然后可以將結(jié)構(gòu)傳遞,而不是只使用整形數(shù)值來傳遞。 smorgasboard 這個例子可以讓你體會支持的互操作特性。
其他各種各樣的特性例如從全局范圍內(nèi)導入(就像alert函數(shù)),在Rust中使用一個Result來獲取JS異常,以及在Rust程序中通用方法模擬存儲JS值。
如果你想了解更多的功能,繼續(xù)閱讀 issue tracker。
3、wasm-bindgen接下來做什么?
在我們結(jié)束之前,我想花一點時間來下描述wasm-bindgen的未來愿景,因為我認為這是當今項目最激動人心的一方面。
不僅僅支持Rust
從第1天起,wasm-bindgen CLI工具就設(shè)計成了多語言支持的。盡管Rust目前是唯一被支持的語言,但該工具也可以嵌入C或C++。 #[wasm_bindgen]屬性創(chuàng)建了可被wasm-bindgen工具解析并隨后刪除的輸出(* .wasm)文件的自定義部分。
本節(jié)介紹要生成哪些JS綁定以及它們的接口是什么。這個描述中沒有關(guān)于Rust的特定部分,因此C ++編譯器插件可以很容易地創(chuàng)建該部分,并通過wasm-bindgen工具進行處理。
我覺得這個方面特別令人振奮,因為我相信它使像wasm-bindgen這樣的工具成為WebAssembly和JS集成的標準做法。希望所有編譯為WebAssembly的語言都能受益,并且可以被bundler自動識別,以避免上述幾乎所有的配置和構(gòu)建工具。
自動綁定JS生態(tài)
使用#[wasm_bindgen] 宏導入功能唯一不好的一面就是你必須將所有東西都寫出來,還要保證沒有任何錯誤。這種讓人覺得很單調(diào)(而且易錯)的操作的自動化技術(shù)已經(jīng)成熟了。
所有的web APIs都由WebIDL指定,而且在generate #[wasm_bindgen] annotations from WebIDL是可行的。這個就意味著你不需要像前面一樣定義alert函數(shù),而是你只需要寫下面這些:
代碼
#[wasm_bindgen] pub fn greet(s: &str) { webapi::alert(&format!("Hello, {}!", s)); }
在這個例子中,WebIDL對web APIs的描述可以完全自動生成webapi集合,保證沒有錯誤。
我們甚至可以將自動化更進一步,TypeScript組織已經(jīng)做了這方面的復雜工作,參照generate #[wasm_bindgen] from TypeScript as well??梢悦赓M用npm上的TypeScript自動綁定任何包!
比 JS DOM 操作更快的性能
最后要說的事情對 wasm-bindgen 來說也很重要:超快的 DOM 操作 —— 這是很多 JS 框架的終極目標。如今需要使用一些中間工具來調(diào)用 DOM 函數(shù),這些工具正在由 JavaScript 實現(xiàn)轉(zhuǎn)向 C++ 引擎實現(xiàn)。然而,在 WebAssembly 來臨之后,這些工具并非必須。WebAssembly 是有類型的。
從第一天起,wasm-bindgen 代碼生成的設(shè)計就考慮到了將來的宿主綁定方案。當這一特征出現(xiàn)在 WebAssembly 之后,我們可以直接調(diào)用導入的函數(shù),而不需要 wasm-bindgen 的中間工具。
此外,它使得 JS 引擎積極優(yōu)化 WebAssembly 對 DOM 的操作,使其對類型的支持更好,而且在調(diào)用 JS 的時候不再需要進行參數(shù)驗證。在這一點上,wasm-bindgen 不僅在操作像 string 這樣的富類型變得容易,還提供了一流的 DOM 操作性能。
以上是“JavaScript和Rust中wasm-bindgen組件有什么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!