Rust Web 全栈开发(十一):WebAssembly 尝鲜
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 命令。