2021-05-06 WebAssembly
開發(fā)環(huán)境
操作系統(tǒng)
目前用的操作系統(tǒng)是windows。
Node
開發(fā)環(huán)境 。
先下載一個Nodejs。
當(dāng)前版本: 14.16.1
[圖片上傳失敗...(image-842b47-1620292429591)]
Rust
這個就不介紹怎么安裝了,
我的版本是
[圖片上傳失敗...(image-8913b1-1620292429591)]
教程參考
編譯成WebAssembly 有兩個教程。
還有另外一個是 ,不過這個用的是他們自己寫的一個構(gòu)建工具。
還有一本書,建議深入的時候讀。上面教程都是非常簡單的。書比較系統(tǒng)
動手
我們先按官方教程擼一把。
在本教程中,我們將使用 Rust 的 npm 包構(gòu)建工具 wasm-pack 來構(gòu)建一個 npm 包。這個包只包含 WebAssembly 和 JavaScript 代碼,以便包的用戶無需安裝 Rust 就能使用。他們甚至不需要知道這里包含 WebAssembly!
安裝wasm-pack
要構(gòu)建我們的包,我們需要一個額外工具 wasm-pack。它會幫助我們把我們的代碼編譯成 WebAssembly 并制造出正確的 npm 包。使用下面的命令可以下載并安裝它:
$ cargo install wasm-pack
構(gòu)建我們的 WebAssembly npm 包
萬事俱備,來創(chuàng)建一個新的 Rust 包吧。打開你用來存放私人項目的目錄,我的目錄是 d:/workspace:
$ cargo new --lib hello-wasm
[圖片上傳失敗...(image-eca9bb-1620292429591)]
這里會在名為 hello-wasm 的子目錄里創(chuàng)建一個新的庫,里面有下一步之前我們所需要的一切:
+-- Cargo.toml
+-- src
+-- lib.rs
首先,我們有一個 Cargo.toml 文件,這是我們配置構(gòu)建的方式。如果你用過 Bundler 的 Gemfile 或者 npm 的 package.json,你應(yīng)該會感到很熟悉。Cargo 的用法和它們類似。
接下來,Cargo 在 src/lib.rs 生成了一些 Rust 代碼:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
我們完全不需要使用這些測試代碼,所以繼續(xù)吧,我們刪掉它。
修改Cargo.toml
接下來不太按教程的套路走了,如果還按教程的套路,那么不如直接自己看教程好了。
首先我們需要去修改 Cargo.toml 添加下列內(nèi)容
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
第一個部分 — [lib] — 告訴 Rust 為我們的包建立一個 cdylib 版本;官方解釋
--crate-type=cdylib,#[crate_type = "cdylib"]- A dynamic system library will be produced. This is used when compiling a dynamic library to be loaded from another language. This output type will create*.sofiles on Linux,*.dylibfiles on macOS, and*.dllfiles on Windows.
第二個部分是 [dependencies] 部分。在這里我們告訴 Cargo 我們需要依賴哪個版本的 wasm-bindgen ;
[圖片上傳失敗...(image-156bc0-1620292429591)]
我們到crate.io 上看一下,最新版本還是0.2.73
寫點RUST代碼吧。
讓我們在 src/lib.rs 寫一些代碼替換掉原來的:
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn say(name: &str) {
alert(&format!("Hello, {}!", name));
}
這就是我們這個 Rust 項目的內(nèi)容。它有三個主要部分,讓我們按順序來講。
使用 wasm-bindgen 在 Rust 與 JavaScript 之間通信
第一部分看起來像這樣:
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
第一行就像在說 “哇 Rust,我們在用一個叫做 wasm_bindgen 的庫”。在 Rust 當(dāng)中,庫被稱為 “crates”,因為我們使用的是一個外部庫,所以有 "extern"。通過 extern crate + crate名稱將包內(nèi)的模塊引入當(dāng)前環(huán)境。
在使用 extern 引入包之后,可以用 use 將模塊引入到當(dāng)前作用域。
第三行包括了一個將庫中的代碼引入到你的代碼中的使用命令。在這個情況下,將會引入 wasm_bindgen::prelude 的全部模塊。
在我們開始下一節(jié)之前,我們將講一講 wasm-bindgen.
wasm-pack 使用 wasm-bindgen,其它工具,去提供一個連接 JavaScript 和 Rust 的橋。它允許 JavaScript 使用 string 調(diào)用 Rust API,或者調(diào)用一個 Rust function 去捕獲 JavaScript 異常。
我們將在我們的包中使用 wasm-bindgen 的功能。事實上,這是下一節(jié)的內(nèi)容!
在 Rust 中調(diào)用來自 JavaScript 的外部函數(shù)
接下來的部分看起來像這樣:
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
在 #[] 中的內(nèi)容叫做 "屬性",并以某種方式改變下面的語句。在這種情況下,下面的語句是一個 extern,它將告訴 Rust 我們想調(diào)用一些外部定義的函數(shù)。這個屬性告訴我們 "wasm-bindgen 知道如何找到這些函數(shù)"。
第三行是用 Rust 寫的函數(shù)簽名。它告訴我們 "alert 函數(shù)接受一個叫做 s 的字符串作為參數(shù)。"
你可能會疑惑這個函數(shù)是什么,你的疑惑可能是正確的:這是the alert function provided by JavaScript!
編寫能夠在JavaScript中調(diào)用的 Rust 函數(shù)
最后一部分是這樣的:
#[wasm_bindgen]
pub fn say(name: &str) {
alert(&format!("Hello, {}!", name));
}
我們又看到了 #[wasm_bindgen] 屬性。在這里,它并非定義一個 extern 塊,而是 fn,這代表我們希望能夠在 JavaScript 中使用這個 Rust 函數(shù)。 這和 extern 正相反:我們并非引入函數(shù),而是要把函數(shù)給外部世界使用。
這個函數(shù)的名字是 say,它需要一個參數(shù),一個字符串 (寫作 &str)。它調(diào)用了我們前面在 extern 塊中引入的 alert 函數(shù)。它傳遞了一個讓我們串聯(lián)字符串的 format! 宏的調(diào)用。
在這里有兩個參數(shù),一個格式化字符串和一個要填入的變量。格式化字符串是 "Hello, {}!" 部分。它可以包含一個或多個 {},變量將會被填入其中。傳遞的變量是 name,也就是這個函數(shù)的參數(shù)。所以當(dāng)我們調(diào)用 greet("Steve")時我們就能看到 "Hello, Steve!"。
這個傳遞到了 alert(),所以當(dāng)我們調(diào)用這個函數(shù)時,我們應(yīng)該能看到他談彈出了一個帶有 "Hello, Steve!" 的消息框。
我們的庫寫完了,是時候構(gòu)建它了。
構(gòu)建包
現(xiàn)在我們已經(jīng)完成了所有配置項,開始構(gòu)建吧!在命令行輸入以下命令:
$ wasm-pack build
-
wasm-pack build, 構(gòu)建生成 pkg 文件夾; -
wasm-pack test用于測試
這個命令將做一系列事情 (這會花一些時間,特別是當(dāng)你第一次運(yùn)行 wasm-pack)。想了解詳細(xì)情況,查看這篇在 Mozilla Hacks 上的文章。簡單來說,wasm-pack build 將做以下幾件事:
- 將你的 Rust 代碼編譯成 WebAssembly。
- 在編譯好的 WebAssembly 代碼基礎(chǔ)上運(yùn)行
wasm-bindgen,生成一個 JavaScript 文件將 WebAssembly 文件包裝成一個模塊以便 npm 能夠識別它。 - 創(chuàng)建一個
pkg文件夾并將 JavaScript 文件和生成的 WebAssembly 代碼移到其中。 - 讀取你的
Cargo.toml并生成相應(yīng)的package.json。 - 復(fù)制你的
README.md(如果有的話) 到文件夾中。
[圖片上傳失敗...(image-438fd-1620292429591)]
[圖片上傳失敗...(image-29f44-1620292429591)]
.......
[圖片上傳失敗...(image-359e3a-1620292429591)]
第一次編譯非常非常多東西.....
最后的結(jié)果?你在 pkg 文件夾下有了一個 npm 包。
我們看看里面有什么東西
[圖片上傳失敗...(image-4eb720-1620292429591)]
對代碼體積的一些說明
如果你檢查生成的 WebAssembly 文件體積,它可能有幾百 kB。我們沒有讓 Rust 去壓縮生成的代碼,從而大大減少生成包的體積。這和本次教程主題無關(guān),但如果你想了解更多,查看 Rust WebAssembly 工作組文檔上關(guān)于 減少 .wasm 體積 的說明。真的有用的傳送門
我們先看看現(xiàn)在編譯出來的wasm有多大?
[圖片上傳失敗...(image-522d02-1620292429591)]
然后我們按照教程上的
Cargo.toml里面進(jìn)行修改
[profile.release]
lto = true
這為 LLVM 提供了更多內(nèi)聯(lián)和修剪功能的機(jī)會。它不僅會變小,而且還會使它在運(yùn)行時間更快!缺點是編譯需要更長的時間。
LLVM 的優(yōu)化通行證經(jīng)過調(diào)整,以在默認(rèn)情況下提高速度,而不是尺寸。我們可以通過將該部分修改為:
[profile.release]
lto = true
opt-level = 's'
[圖片上傳失敗...(image-654412-1620292429591)]
有效果哦??!
或者,更積極地優(yōu)化尺寸,進(jìn)一步的潛在速度成本:
[profile.release]
lto = true
opt-level = 'z'
[圖片上傳失敗...(image-b7c211-1620292429591)]
多了一點....
我們建立一個使用我們包的網(wǎng)站
讓我們建立一個使用我們包的網(wǎng)站! 人們通過各種打包工具使用 npm包,在本教程中,我們將使用 webpack。 它比其他某些打包工具稍微復(fù)雜一點,但展示了更實際的用法。
讓我們離開pkg目錄,并創(chuàng)建一個新目錄site,嘗試以下操作:
創(chuàng)建一個新文件 package.json,然后輸入如下代碼:
{
"scripts": {
"serve": "webpack-dev-server"
},
"dependencies": {
"@mynpmusername/hello-wasm": "^0.1.0"
},
"devDependencies": {
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
我們沒有像教程那樣上傳到npm中。我們需要對 dependencies 進(jìn)行修改一下。指向相對地址
- dependencies: 表示生產(chǎn)環(huán)境下的依賴管理;無論開發(fā)還是生產(chǎn)都會下載使用的依賴
- devDependencies: 表示開發(fā)環(huán)境下的依賴管理;
{
"scripts": {
"serve": "webpack-dev-server"
},
"dependencies": {
"hello_wasm": "file:../pkg"
},
"devDependencies": {
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
接下來,我們需要配置Webpack。 創(chuàng)建 webpack.config.js 并輸入:
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development"
};
現(xiàn)在我們需要一個HTML文件。 創(chuàng)建一個index.html并寫入如下內(nèi)容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hello-wasm example</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
最后,從HTML文件中引用index.js:
const js = import("../pkg/hello_wasm.js");
js.then(js => {
js.say("WebAssembly");
});
這將從pkg文件夾導(dǎo)入我們的模塊。這不是最佳做法,但這里只做一個演示,因此暫時就這樣用。 加載后,它將從該模塊調(diào)用say函數(shù),并傳入字符串“WebAssembly”參數(shù)。注意這里看上去沒有什么特別的,但是我們正在調(diào)用 Rust 代碼! 就JavaScript代碼所知,這只是一個普通模塊。
我們已經(jīng)完成了所有的文件! 讓我們試一下:
$ npm install
$ npm run serve
[圖片上傳失敗...(image-a4d2b-1620292429591)]
我們看一下。寫的wasm加載到了前端了。
[圖片上傳失敗...(image-1019c7-1620292429591)]
打完收工