wasm-pack 工具链
wasm-pack 是什么?
wasm-pack 是 Rust WebAssembly 工作组开发的官方工具,它整合了 Rust 编译器(rustc)、wasm-bindgen(JS/Wasm 绑定生成器)和 wasm-opt(体积优化工具),让 Rust → Wasm 的编译流程极其简单。
安装工具链
# 安装 Rust(如果还没有)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# 添加 Wasm 编译目标
rustup target add wasm32-unknown-unknown
# 安装 wasm-pack
cargo install wasm-pack
# 或者更快的安装方式:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
创建 Rust Wasm 库
# 创建新的 Rust 库项目
cargo new --lib hello-wasm
cd hello-wasm
# Cargo.toml
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[lib]
# 编译为 C 动态库(wasm-pack 需要)
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
[dev-dependencies]
wasm-bindgen-test = "0.3"
#[wasm_bindgen] 宏
// src/lib.rs
use wasm_bindgen::prelude::*;
// 导出函数到 JavaScript
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 导出结构体
#[wasm_bindgen]
pub struct Counter {
count: u32,
}
#[wasm_bindgen]
impl Counter {
#[wasm_bindgen(constructor)]
pub fn new() -> Counter {
Counter { count: 0 }
}
pub fn increment(&mut self) {
self.count += 1;
}
pub fn get_count(&self) -> u32 {
self.count
}
}
// 从 JavaScript 导入函数
#[wasm_bindgen]
extern "C" {
pub fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
编译与使用
# 编译为 Web 目标(ES Modules)
wasm-pack build --target web
# 编译为 bundler 目标(Webpack/Vite)
wasm-pack build --target bundler
# 编译为 Node.js 目标
wasm-pack build --target nodejs
# Release 模式(开启优化)
wasm-pack build --release --target web
<!-- 在 HTML 中使用(target: web)-->
<script type="module">
import init, { add, Counter } from './pkg/hello_wasm.js';
async function run() {
await init(); // 加载并初始化 Wasm 模块
console.log(add(2, 3)); // 5
const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.get_count()); // 2
}
run();
</script>
pkg/ 目录结构
pkg/
├── hello_wasm.js # JS 胶水代码(自动生成)
├── hello_wasm.d.ts # TypeScript 类型声明(自动生成)
├── hello_wasm_bg.wasm # 实际的 Wasm 二进制
├── hello_wasm_bg.wasm.d.ts
└── package.json
wasm-bindgen 深度解析
#[wasm_bindgen] 宏的工作原理
#[wasm_bindgen] 是一个过程宏(Procedural Macro),它在编译时分析标注的 Rust 代码,自动生成:
Wasm 侧胶水代码
额外的 Rust 函数,负责将 JavaScript 传来的 JS 类型转换为 Rust 类型(如:从线性内存读取字符串、将 JS 对象转为 Rust 结构体)。这些函数会被编译进 .wasm 文件。
JavaScript 胶水代码(.js 文件)
自动生成的 JS 包装器,暴露类型友好的 JS API。例如,如果你的 Rust 函数接收 &str,JS 侧会自动处理字符串编码,你在 JS 中只需传普通字符串。
TypeScript 类型声明(.d.ts 文件)
为生成的 JS 绑定自动创建 TypeScript 类型声明,让 TypeScript 项目有完整的类型提示。
wasm-bindgen 支持的类型
use wasm_bindgen::prelude::*;
// 基础类型 — 直接映射到 Wasm 数值类型
#[wasm_bindgen]
pub fn numeric_types(
i: i32, // → Wasm i32
u: u32, // → Wasm i32(JS 侧视为无符号)
f: f64, // → Wasm f64
b: bool, // → Wasm i32(0 或 1)
) -> f64 { f }
// 字符串 — 通过内存传递,wasm-bindgen 自动编解码
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
// name 是从 JS 内存复制到 Rust 字符串的,这里有一次内存分配
format!("Hello, {}!", name)
}
// 可选类型 — 使用 Option
#[wasm_bindgen]
pub fn maybe_add(a: i32, b: Option<i32>) -> i32 {
a + b.unwrap_or(0)
// JS 中可以传 null/undefined 给 Option 参数
}
// 错误处理 — 返回 Result 可以抛出 JS 异常
#[wasm_bindgen]
pub fn parse_number(s: &str) -> Result<f64, JsValue> {
s.parse::<f64>()
.map_err(|e| JsValue::from_str(&e.to_string()))
// 如果解析失败,JS 侧会收到一个 throw 的 Error 对象
}
// Vec<u8> — 通过 Uint8Array 传递,适合图像/音频数据
#[wasm_bindgen]
pub fn process_bytes(data: &[u8]) -> Vec<u8> {
// data 以 Uint8Array 传入,返回值也是 Uint8Array
data.iter().map(|&b| 255 - b).collect()
}
wasm-bindgen 与 JavaScript 对象交互
use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, HtmlCanvasElement, CanvasRenderingContext2d};
// web-sys crate 提供所有 Web API 的 Rust 绑定
#[wasm_bindgen]
pub fn draw_circle(canvas_id: &str) -> Result<(), JsValue> {
// 获取 DOM 中的 canvas 元素
let document: Document = web_sys::window()
.ok_or("no window")? // window 可能不存在(非浏览器环境)
.document()
.ok_or("no document")?;
let canvas = document
.get_element_by_id(canvas_id)
.ok_or("canvas not found")?
.dyn_into::<HtmlCanvasElement>()?; // 动态类型转换
let ctx = canvas
.get_context("2d")?
.ok_or("failed to get 2d context")?
.dyn_into::<CanvasRenderingContext2d>()?;
// 绘制填充圆形
ctx.begin_path();
ctx.arc(150.0, 150.0, 100.0, 0.0, std::f64::consts::TAU)?;
ctx.set_fill_style(&JsValue::from_str("#654FF0"));
ctx.fill();
Ok(())
}
wasm-pack 编译目标详解
--target web
生成 ES 模块(ESM),使用
import init from './pkg/...',需要先调用 await init() 初始化。适合直接在浏览器中使用,不经过打包工具。--target bundler
默认目标,生成适合 Webpack/Vite/Rollup 处理的模块。打包工具会处理 Wasm 的加载细节,用户代码可以直接 import 导出的函数,无需手动 init。
--target nodejs
生成 CommonJS 模块,同步加载 Wasm 文件。适合 Node.js 后端使用,通过
require() 导入。--target deno
生成 Deno 兼容的 ES 模块,使用
import 和 URL 导入。Deno 对 Wasm 的支持与浏览器 API 完全兼容。Cargo.toml 优化配置
# Cargo.toml — 用于生产的优化配置
[package]
name = "my-wasm-lib"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"] # cdylib 是生成 .wasm 的必要 crate 类型
[dependencies]
wasm-bindgen = "0.2"
# web-sys:提供浏览器 Web API 的 Rust 绑定
[dependencies.web-sys]
version = "0.3"
features = ["Window", "Document", "HtmlCanvasElement"]
# 注意:只启用需要的 feature,否则编译时间大幅增加
# Release 模式优化
[profile.release]
opt-level = "z" # z = 体积优化(vs 3 = 速度优化),Wasm 通常优先体积
lto = true # 链接时优化(跨 crate 死代码消除),可减少 30-40% 体积
panic = "abort" # abort 比 unwind 减少约 20KB 的 panic 展开代码
常见问题与调试
内存泄漏:Rust 结构体在 JS 侧的生命周期
用 #[wasm_bindgen] 标注的 Rust 结构体在 JS 中通过引用计数管理。当 JS 对象被垃圾回收时,Rust 侧的 drop 会被调用。但如果结构体持有大量资源(如缓冲区),最好显式调用 .free() 方法来提前释放:
const processor = new ImageProcessor();
processor.process(imageData);
processor.free(); // 立即释放 Rust 侧内存,不要等 GC
字符串传递的隐藏开销
每次从 JS 传字符串到 Rust,wasm-bindgen 都会:(1) 在 JS 中调用 TextEncoder 编码为 UTF-8;(2) 在 Wasm 内存中分配空间;(3) 复制数据。对于大字符串或高频调用,这个开销不可忽视。考虑改用 &[u8] 直接传字节数组,或使用共享内存(SharedArrayBuffer)避免复制。
在 Wasm 中不能使用 Rust 的 std::io
wasm32-unknown-unknown 目标没有文件系统或网络访问,std::io::File、std::net::TcpStream 等无法编译。如果需要系统调用,使用 wasm32-wasip1 目标配合 WASI 运行时(见第8章)。纯计算库不受影响。
本章小结
本章核心要点
- wasm-pack 是官方工具链:整合 rustc + wasm-bindgen + wasm-opt,一条命令完成 Rust → Wasm → npm 包的完整流程
- #[wasm_bindgen] 原理:过程宏自动生成类型转换代码、JS 胶水层和 TypeScript 声明,免去手动内存操作
- 类型支持:i32/u32/f64/bool 直接映射;String/&str 通过 TextEncoder 自动处理;Option/Result 映射到 null/throw
- web-sys:提供全部 Web API 的 Rust 绑定,按需启用 feature 减少编译时间
- 生产优化:opt-level="z" + lto=true + panic="abort",通常可将 .wasm 体积减少 50%+
- 关键陷阱:JS 结构体需显式调用 .free();字符串传递有编码开销;wasm32-unknown-unknown 无 std::io