Rust Web 全棧開發(十一):WebAssembly 嘗鮮
- Rust Web 全棧開發(十一):WebAssembly 嘗鮮
- 什么是 WebAssembly?
- 安裝 wasm-pack 和 cargo-generate
- 使用項目模板
- 構建項目
- 生成網頁
- 安裝依賴項
- 在 www 中使用本地的 wasm-pack-template 包
- 本地服務
Rust Web 全棧開發(十一):WebAssembly 嘗鮮
參考視頻:https://www.bilibili.com/video/BV1RP4y1G7KF
繼續之前的 Actix 項目。
我們已經實現了一個 Web App,在網頁端查看并操作數據庫中教師的數據。現在我們想創建一個 WebAssembly App,查看并操作數據庫中課程的數據。
什么是 WebAssembly?
WebAssembly 是一種新的編碼方式,可以在現代瀏覽器中運行。
- 它是一種低級的類匯編語言
- 具有緊湊的二進制格式
- 可以接近原生的性能運行
- 并為 C/C++、C#、Rust 等語言提供一個編譯目標,以便它們可以在 Web 上運行
- 它也被設計為可以與 JavaScript 共存,允許兩者一起工作
WebAssembly 不是匯編語言,它不針對特定的機器,而是中間編譯器目標,針對瀏覽器。
WebAssembly 有兩種格式:
- 文本格式,后綴為 .wat
- 二進制格式,后綴為 .wasm
WebAssembly 能做什么?
- 可以把你編寫 C/C++、C#、Rust 等語言的代碼編譯成 WebAssembly 模塊
- 你可以在 Web 應用中加載該模塊,并通過 JavaScript 調用它
- 它并不是為了替代 JS,而是與 JS 一起工作
- 仍然需要 HTML 和 JS,因為 WebAssembly 無法訪問平臺 API,例如 DOM、WebGL …
一個 C 語言的例子:
-
快速、高效、可移植:通過利用常見的硬件能力,WebAssembly 代碼在不同平臺上能夠以接近本地速度
運行。 -
可讀、可調試:WebAssembly 是一門低階語言,但是它有確實有一種人類可讀的文本格式(其標
準最終版本目前仍在編制),這允許通過手工來寫代碼,看代碼以及調試代碼。 -
保持安全:WebAssembly 被限制運行在一個安全的沙箱執行環境中。像其他網絡代碼一樣,它遵循瀏覽器的同源策略和授權策略。
-
不破壞網絡:WebAssembly 的設計原則是與其他網絡技術和諧共處并保持向后兼容。
安裝 wasm-pack 和 cargo-generate
官方文檔:https://rustwasm.github.io/docs/book/game-of-life/setup.html
-
wasm-pack 是構建、測試和發布 Rust 生成的 WebAssembly 的一站式商店。
-
cargo-generate 通過利用已有的 git 存儲庫作為模板,幫助你快速啟動并運行新的 Rust 項目。
命令行執行以下兩個命令:
cargo install wasm-pack
cargo install cargo-generate
使用項目模板
官方文檔:https://rustwasm.github.io/docs/book/game-of-life/hello-world.html
項目模板預先配置了相同的默認值,因此你可以快速構建、集成和打包 Web 代碼。
回到 Actix 項目的最頂層,在終端中用下面的命令克隆項目模板:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
這里我一直 git 不下來:
就直接下載 zip,解壓到 Actix 項目的最頂層了。
因為是手動添加的 wasm-pack-template,需要將其手動添加為 members:
先來看看 wasm-pack-template 的 Cargo.toml:
[package]
name = "{{project-name}}"
version = "0.1.0"
authors = ["{{authors}}"]
edition = "2018"[lib]
crate-type = ["cdylib", "rlib"][features]
default = ["console_error_panic_hook"][dependencies]
wasm-bindgen = "0.2.84"# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.7", optional = true }[dev-dependencies]
wasm-bindgen-test = "0.3.34"[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"
其中最核心的是依賴中的 wasm-bindgen,用于綁定生成器。
修改一下字段:
再來看 src 目錄下的 lib.rs,這是我們要編譯到 WebAssembly 的 Rust crate 的根文件。它使用 wasm-bindgen 與 JavaScript 進行交互。它導入 JavaScript 函數 alert,并導出 Rust 函數 greet,該函數會發出問候消息的警報。
mod utils;use wasm_bindgen::prelude::*;#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;#[wasm_bindgen]
extern {fn alert(s: &str);
}#[wasm_bindgen]
pub fn greet() {alert("Hello, wasm-pack-template!");
}
在 Rust 中,想要調用前端的函數,需要這樣聲明:
#[wasm_bindgen]
extern {fn alert(s: &str);
}
Rust 函數想要被前端調用,需要加上 #[wasm_bindgen]。
在前端中,想要調用 Rust 的函數,需要這樣聲明:
#[wasm_bindgen]
pub fn greet() {alert("Hello, wasm-pack-template!");
}
將 alert 中的字符串改為 “Hello, wasm-pack-template!”。
utils.rs 模塊提供了常用的實用程序,使 Rust 更容易編譯到 WebAssembly。例如,在調試 wasm 代碼時,這個文件很有用,但是我們現在可以忽略這個文件。
構建項目
我們使用 wasm-pack 編排以下構建步驟:
-
確保我們有 Rust 1.30 或更新的版本,并且通過 Rust 安裝了 wasm32-unknown-unknown 目標。
-
通過 cargo 將 Rust 源代碼編譯成 WebAssembly .wasm 二進制文件。
-
使用 wasm-bindgen 來生成 JavaScript API,以便使用 Rust 生成的 WebAssembly。
要完成所有這些,cd 到 wasm-pack-template 目錄下,執行命令:
wasm-pack build
構建成功:
當構建完成后,我們可以在 pkg 目錄中找到它的工件:
下面講解幾個重要的文件。
wasm-pack-template/pkg/wasm-pack-template_bg.wasm:由 Rust 編譯器從 Rust 源代碼生成的 WebAssembly 二進制文件。它包含所有 Rust 函數和數據的編譯到 wasm 的版本。例如,它有一個導出的 greet 函數。
wasm-pack-template/pkg/wasm-pack-template.js:
import * as wasm from "./wasm_pack_template_bg.wasm";
export * from "./wasm_pack_template_bg.js";
import { __wbg_set_wasm } from "./wasm_pack_template_bg.js";
__wbg_set_wasm(wasm);
wasm.__wbindgen_start();
wasm-pack-template/pkg/wasm-pack-template.js 是包含 JavaScript 的膠水代碼,由 wasm-bindgen 生成。它包含 JavaScript glue,用于將 DOM 和 JavaScript 函數導入 Rust,并將 WebAssembly 中的函數暴露給 JavaScript。
例如,有一個 JavaScript greet 函數,它包裝了從 WebAssembly 模塊導出的 greet 函數。現在,這種粘合并沒有做太多的事情,但是當我們開始在 wasm 和 JavaScript 之間來回傳遞更多值時,它將幫助引導這些值跨越邊界。
這里有問題,我把 wasm-pack-template/pkg/wasm-pack-template.js 改成了這樣:
// import * as wasm from "./wasm_pack_template_bg.wasm";
import * as wasm from "./wasm_pack_template";
export * from "./wasm_pack_template_bg.js";
// import { __wbg_set_wasm } from "./wasm_pack_template_bg.js";
// __wbg_set_wasm(wasm);
// wasm.__wbindgen_start();
export function greet() {alert("Hello, wasm-pack-template!");
}
wasm-pack-template/pkg/wasm-pack-template.d.js:
/* tslint:disable */
/* eslint-disable */
export function greet(): void;
此類 .d.js 文件包含用于 JavaScript glue 的 TypeScript 類型聲明。如果你使用 TypeScript,你可以檢查調用 WebAssembly 函數的類型。如果你不使用 TypeScript,你可以安全地忽略這個文件。
wasm-pack-template/pkg/package.json:
{"name": "wasm-pack-template","type": "module","collaborators": ["xiye <812288728@qq.com>"],"version": "0.1.0","files": ["wasm_pack_template_bg.wasm","wasm_pack_template.js","wasm_pack_template_bg.js","wasm_pack_template.d.ts"],"main": "wasm_pack_template.js","types": "wasm_pack_template.d.ts","sideEffects": ["./wasm_pack_template.js","./snippets/*"]
}
package.json 文件包含生成的 JavaScript 和 WebAssembly 包的元數據。它被 npm 和 JavaScript 打包器用來確定包之間的依賴關系、包名、版本和其他一些東西。它幫助我們集成 JavaScript 工具,并允許我們將包發布到 npm。
生成網頁
要獲取 wasm-pack-template 包并在 Web 頁面中使用它,我們需要使用 create-wasm-app 這個 JavaScript 項目模板。
首先下載 create-wasm-app 這個庫:
npm install -g create-wasm-app
在 wasm-pack-template 目錄下運行這個命令:
npm init wasm-app www
構建成功:
在 wasm-pack-template 目錄下生成了一個 www 目錄,與 pkg 目錄并列:
讓我們仔細看看其中的一些文件。
wasm-pack-template/www/package.json:
{"name": "create-wasm-app","version": "0.1.0","description": "create an app to consume rust-generated wasm packages","main": "index.js","bin": {"create-wasm-app": ".bin/create-wasm-app.js"},"scripts": {"build": "webpack --config webpack.config.js","start": "webpack-dev-server"},"repository": {"type": "git","url": "git+https://github.com/rustwasm/create-wasm-app.git"},"keywords": ["webassembly","wasm","rust","webpack"],"author": "Ashley Williams <ashley666ashley@gmail.com>","license": "(MIT OR Apache-2.0)","bugs": {"url": "https://github.com/rustwasm/create-wasm-app/issues"},"homepage": "https://github.com/rustwasm/create-wasm-app#readme","devDependencies": {"hello-wasm-pack": "^0.1.0","webpack": "^4.29.3","webpack-cli": "^3.1.0","webpack-dev-server": "^3.1.5","copy-webpack-plugin": "^5.0.0"}
}
這個 package.json 預先配置了 webpack 和 webpack-dev-server 依賴項,以及對 hello-wasm-pack 的依賴項,它是發布到 npm 的初始 wasm-pack-template 包的一個版本。
wasm-pack-template/www/webpack.config.js:
const CopyWebpackPlugin = require("copy-webpack-plugin");
const path = require('path');module.exports = {entry: "./bootstrap.js",output: {path: path.resolve(__dirname, "dist"),filename: "bootstrap.js",},mode: "development",plugins: [new CopyWebpackPlugin(['index.html'])],
};
該文件配置 webpack 及其本地開發服務器。它是預先配置的,你根本不需要調整它來讓 webpack 和它的本地開發服務器工作。
wasm-pack-template/www/index.html:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Hello wasm-pack!</title></head><body><noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript><script src="./bootstrap.js"></script></body>
</html>
這是 Web 頁面的根 HTML 文件。除了加載 bootstrap.js 之外,它沒有做太多的事情,這是 index.js 的一個非常薄的包裝。
wasm-pack-template/www/index.js:
import * as wasm from "hello-wasm-pack";wasm.greet();
這是 Web 頁面 JavaScript 的主要入口點。它導入 hello-wasm-pack npm 包,其中包含默認的 wasm-pack-template 的編譯 WebAssembly 和 JavaScript glue,然后調用 hello-wasm-pack 的 greet 函數。
安裝依賴項
首先,通過在 wasm-pack-template/www 子目錄下運行 npm install,確保本地開發服務器及其依賴已經安裝:
這個命令只需要運行一次,就會安裝 webpack JavaScript 打包器和它的開發服務器。
注意,使用 Rust 和 WebAssembly 并不需要 webpack,它只是我們為了方便而選擇的打包器和開發服務器。Parcel 和 Rollup 還應該支持將 WebAssembly 作為 ECMAScript 模塊導入。如果你愿意,你也可以使用 Rust 和 WebAssembly 而不使用捆綁器。
在 www 中使用本地的 wasm-pack-template 包
向 wasm-pack-template/www/package.json 中添加依賴:
"dependencies": {"wasm-pack-template": "file:../pkg"},
修改 wasm-pack-template/www/index.js,不使用 hello-wasm-pack 中的 greet 函數,而使用我們的 greet 函數:
// import * as hello_wasm from "hello-wasm-pack";
import * as wasm from "wasm-pack-template";wasm.greet();
因為依賴有修改,需要再次在 wasm-pack-template/www 子目錄下運行 npm install:
現在,我們的 Web 頁面現在可以在本地訪問了!
本地服務
接下來,為開發服務器打開一個新終端。在新終端中運行服務器可以讓它在后臺運行,并且不會阻止我們同時運行其他命令。在新的終端中,從 wasm-pack-template/www 目錄中運行以下命令:
npm run start
構建成功:
將 Web 瀏覽器導航到 localhost:8081/,應該會看到一條警告消息:
任何時候進行更改并希望它們反映在 localhost:8081/ 上,只需在 wasm-pack-template 目錄中重新運行 wasm-pack build 命令。