关于 rust-wasm
rust-wasm 是面向 Rust 开发者的 WebAssembly 实践技能,介绍通过 wasm-pack 和 wasm-bindgen 构建模块及与 JavaScript、浏览器交互。内容还涵盖异步操作、测试、错误处理和体积优化。
主要功能
- 01使用 wasm-pack 构建 WASM
- 02通过 wasm-bindgen 进行 JavaScript 互操作
- 03浏览器 DOM 操作与异步调用
- 04浏览器和 Node.js 测试
- 05错误处理与模块体积优化
适用场景
- 01将 Rust 模块用于浏览器项目
- 02为 JavaScript 项目构建 WASM 包
- 03测试并优化 WebAssembly 模块
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ pluginagentmarketplace/ custom-plugin-rust --skill rust-wasm安装后在 Claude Code 中输入/rust-wasm确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/config.yamlschema.jsonwasm-setup.yamlreferences/GUIDE.mdPATTERNS.mdscripts/validate.py详细介绍
Rust WebAssembly 技能
使用 Rust 精通 WebAssembly:wasm-pack、wasm-bindgen、JavaScript 互操作和浏览器集成。
快速入门
设置
# Install wasm-pack
cargo install wasm-pack
# Add target
rustup target add wasm32-unknown-unknown
# Create project
cargo new --lib my-wasm
cd my-wasm
项目配置
# Cargo.toml
[package]
name = "my-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["console", "Window", "Document"] }
[dev-dependencies]
wasm-bindgen-test = "0.3"
[profile.release]
lto = true
opt-level = "z"
基础 WASM 模块
// src/lib.rs
use wasm_bindgen::prelude::*;
// Import JavaScript function
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Export Rust function to JavaScript
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// Initialize (called on load)
#[wasm_bindgen(start)]
fn main() {
log("WASM module loaded!");
}
构建
# Build for bundlers (webpack, etc.)
wasm-pack build
# Build for web (no bundler)
wasm-pack build --target web
# Build for Node.js
wasm-pack build --target nodejs
# Build optimized
wasm-pack build --release
JavaScript 集成
在浏览器中使用(无需打包工具)
<!DOCTYPE html>
<html>
<head>
<script type="module">
import init, { greet, add } from './pkg/my_wasm.js';
async function run() {
await init();
const message = greet("World");
console.log(message); // "Hello, World!"
const sum = add(2, 3);
console.log(sum); // 5
}
run();
</script>
</head>
<body></body>
</html>
与 Webpack 搭配使用
// index.js
import * as wasm from "my-wasm";
console.log(wasm.greet("Webpack"));
与 npm 搭配使用
# Build and pack
wasm-pack build
cd pkg
npm link
# In JavaScript project
npm link my-wasm
DOM 操作
use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, HtmlElement, Window};
#[wasm_bindgen]
pub fn manipulate_dom() -> Result<(), JsValue> {
// Get window and document
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
// Create element
let div = document.create_element("div")?;
div.set_id("my-div");
div.set_inner_html("<h1>Hello from Rust!</h1>");
// Add to body
document.body().unwrap().append_child(&div)?;
// Query selector
let element = document.query_selector("#my-div")?.unwrap();
// Add event listener
let closure = Closure::wrap(Box::new(move |_event: web_sys::MouseEvent| {
web_sys::console::log_1(&"Clicked!".into());
}) as Box<dyn FnMut(_)>);
element.add_event_listener_with_callback(
"click",
closure.as_ref().unchecked_ref()
)?;
closure.forget(); // Keep closure alive
Ok(())
}
WASM 中的 Async/Await
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, Response};
#[wasm_bindgen]
pub async fn fetch_data(url: &str) -> Result<JsValue, JsValue> {
let opts = RequestInit::new();
opts.set_method("GET");
let request = Request::new_with_str_and_init(url, &opts)?;
let window = web_sys::window().unwrap();
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
let resp: Response = resp_value.dyn_into()?;
let json = JsFuture::from(resp.json()?).await?;
Ok(json)
}
结构体和复杂类型
use wasm_bindgen::prelude::*;
use serde::{Deserialize, Serialize};
#[wasm_bindgen]
pub struct Point {
x: f64,
y: f64,
}
#[wasm_bindgen]
impl Point {
#[wasm_bindgen(constructor)]
pub fn new(x: f64, y: f64) -> Point {
Point { x, y }
}
#[wasm_bindgen(getter)]
pub fn x(&self) -> f64 {
self.x
}
#[wasm_bindgen(getter)]
pub fn y(&self) -> f64 {
self.y
}
pub fn distance(&self, other: &Point) -> f64 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
}
}
// For complex types, use serde
#[derive(Serialize, Deserialize)]
pub struct Config {
name: String,
values: Vec<i32>,
}
#[wasm_bindgen]
pub fn parse_config(json: &str) -> Result<JsValue, JsValue> {
let config: Config = serde_json::from_str(json)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(serde_wasm_bindgen::to_value(&config)?)
}
内存管理
use wasm_bindgen::prelude::*;
// Typed arrays for efficient data transfer
#[wasm_bindgen]
pub fn process_array(data: &[u8]) -> Vec<u8> {
data.iter().map(|x| x.wrapping_add(1)).collect()
}
// Zero-copy view into WASM memory
#[wasm_bindgen]
pub fn get_memory_view() -> js_sys::Uint8Array {
let buffer = vec![1u8, 2, 3, 4, 5];
// Create view into WASM memory
let array = js_sys::Uint8Array::new_with_length(buffer.len() as u32);
array.copy_from(&buffer);
array
}
测试
// tests/web.rs
#![cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_add() {
assert_eq!(my_wasm::add(2, 2), 4);
}
#[wasm_bindgen_test]
fn test_greet() {
assert_eq!(my_wasm::greet("Test"), "Hello, Test!");
}
#[wasm_bindgen_test]
async fn test_async() {
// Test async functions
}
# Run tests in browser
wasm-pack test --chrome
# Run tests in Node
wasm-pack test --node
大小优化
# Cargo.toml
[profile.release]
lto = true
opt-level = "z"
codegen-units = 1
panic = "abort"
# Build optimized
wasm-pack build --release
# Further optimization with wasm-opt
wasm-opt -Oz -o optimized.wasm pkg/my_wasm_bg.wasm
# Check size
ls -lh pkg/*.wasm
常见模式
控制台日志
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
#[wasm_bindgen(js_namespace = console, js_name = log)]
fn log_many(a: &str, b: &str);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
错误处理
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn may_fail(input: &str) -> Result<String, JsValue> {
if input.is_empty() {
return Err(JsValue::from_str("Input cannot be empty"));
}
Ok(format!("Processed: {}", input))
}
故障排查
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| .wasm 体积过大 | 调试构建 | 使用 --release |
| 导入错误 | 目标配置错误 | 让 --target 与使用方式匹配 |
| 内存泄漏 | 闭包未释放 | 使用 .forget() 或进行清理 |
| 类型不匹配 | 复杂类型 | 使用 serde_wasm_bindgen |
调试清单
# 1. Check wasm size
wasm-pack build --release
ls -lh pkg/*.wasm
# 2. Inspect exports
wasm2wat pkg/my_wasm_bg.wasm | head -50
# 3. Browser console
# Open DevTools > Console for JS errors
# 4. Test in Node first
wasm-pack build --target nodejs
node -e "const m = require('./pkg'); console.log(m.greet('test'))"
最佳实践
- 保持 .wasm 体积小 - 使用
opt-level = "z" - 尽量减少 JS/WASM 边界交互 - 批量处理操作
- 使用类型化数组 - 用于传输大量数据
- 处理错误 - 返回
Result<_, JsValue> - 在多种浏览器中测试 - 兼容性各不相同
- 使用 wasm-opt - 进一步缩减体积
资源
评论
支持 Markdown