使用 Rust 开发 WebAssembly 初体验
作为一名主营 Web 前端的开发小哥,学习 Rust 的动力之一自然是希望能将其发力于 Web 方面。个人目前所知 Rust 在前端的应用一个是各种前端工具链使用 Rust 重写,另一方面便是通过 WebAssembly 技术将一些非 JS 语言的库移植到了浏览器内运行,又或是进行一次逼格十足的用 Rust 写前端页面行为。
Rust 环境搭建
复制官网的命令安装即可:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Windows 用户请参阅这里的文档下载 exe 文件来安装
然后验证安装是否成功:
rustc --version
对于国内用户,还有个不可少的步骤便是包镜像配置,个人目前使用的是清华源,编辑 ~/.cargo/config
文件:
[source.crates-io]
replace-with = 'tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
另外不得不说一句,Rust 的工具链真的很完备,一站式包含了 Monorepo、包管理、Lint、构建、用例测试等等,完全不需要自己一个一个去配置。
初始化项目
上述配置后会提供一个叫做 cargo
的命令,它较类似于 NodeJS 中的 npm
, Rust 中通过它来管理项目,可通过 cargo init project-name
来始化目录。本文里我们是会开发 WebAssembly 项目,使用 cargo
初始化目录后还需要手动配置一些其他内容,略微繁琐,由于已经有专门开发 WebAssembly 的命令行工具了,我们直接用它吧。下述会在全局安装 wasm-pack
命令(类似于 npm install some-cmd --global):
cargo install wasm-pack
接着通过它来初始化我们的项目:
wasm-pack new wasm-demo
使用 VSCode 打开该项目,然后安装下编辑器插件 rust-analyzer
,这样变能正常进行 Rust 开发了。
该项目就是一个标准的 Cargo 项目(Cargo.toml
配置文件和 src
源码目录),并已经帮我们配置好了依赖并提供了一个 greet
函数。现在我们便可以根据所学学习的知识来写代码了。
代码实现
下面我们实现高亮 canvas 上的某个区域,我们将会用到 image
crate(crate 在 rust 里指的就是某个包),先在 Cargo.toml
里添加 image
crate(也可以使用 cargo add image
安装最近版本),然后启用 web_sys
crate 要用到的一些 features(没用到的特性不开启,可以减小产物体积):
[dependencies]
image = "0.24.6"
web-sys = { version = "0.3.61", features = ["HtmlCanvasElement", "CanvasRenderingContext2d", "ImageData"] }
然后在 lib.rs
里写入下述内容:
// lib.rs
use image::{imageops, DynamicImage, ImageBuffer};
use wasm_bindgen::{prelude::*, Clamped};
use web_sys::{HtmlCanvasElement, ImageData};
fn to_image(width: u32, height: u32, vec: Vec<u8>) -> DynamicImage {
let img_buffer = ImageBuffer::from_vec(width, height, vec).unwrap();
DynamicImage::ImageRgba8(img_buffer)
}
fn to_canvas_image(width: u32, height: u32, vec: Vec<u8>) -> ImageData {
ImageData::new_with_u8_clamped_array_and_sh(Clamped(&mut vec.clone()), width, height).unwrap()
}
#[wasm_bindgen]
pub fn highlight_canvas(canvas_elem: &HtmlCanvasElement) {
let ctx = canvas_elem
.get_context("2d")
.unwrap()
.unwrap()
.dyn_into::<web_sys::CanvasRenderingContext2d>()
.unwrap();
// 绘制一个红色边框
ctx.set_stroke_style(&"red".into());
ctx.rect(0f64, 0f64, 50f64, 50f64);
ctx.stroke();
// 获取上述区域的数据
let image_data = ctx
.get_image_data(0f64, 0f64, 50f64, 50f64)
.unwrap()
.data()
.to_vec();
// 将上述获取的 canvas 数据转成 `image` crate 所需的格式
let photo_image = to_image(50, 50, image_data);
// 返回高亮后的信息
let filtered_image = imageops::brighten(&photo_image, 100).to_vec();
// 将 rust 数据转回 canvas 支持的数据
let new_canvas_image = to_canvas_image(50, 50, filtered_image);
ctx.put_image_data(&new_canvas_image, 0f64, 0f64).unwrap();
}
然后命令行运行 wasm-pack build --target web
构建项目,会生成一个 pkg
目录,里面是一个配置好的 npm 包,可以直接用在自己的前端项目。我们新建一个 index.html
写入下述内容:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>hello-wasm example</title>
</head>
<body>
<canvas id="canvas" style="border: 1px solid black"></canvas>
<script type="module">
import init, { highlight_canvas } from "./pkg/hello_wasm.js";
await init();
const $canvas = document.querySelector("#canvas");
const ctx = $canvas.getContext("2d");
ctx.rect(0, 0, 200, 100);
ctx.fill();
highlight_canvas($canvas);
</script>
</body>
</html>
然后开启一个 http 服务器访问该文件,便可以看到效果了。