tech

WebAssembly

浏览器中接近原生性能的二进制格式 - 语言无关(Rust/C/Go)、安全沙箱

TL;DR

是什么:基于堆栈的虚拟机的二进制指令格式,在浏览器中运行。

为什么用:接近原生性能、语言无关(Rust、C、Go)、安全沙箱执行。

Quick Start

使用 Rust(推荐)

# 安装 wasm-pack
cargo install wasm-pack

# 创建项目
cargo new --lib my-wasm
cd my-wasm

配置 Cargo.toml

[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"

编写 Rust 代码src/lib.rs):

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

#[wasm_bindgen]
pub fn greet(name: &str) -> String {
    format!("Hello, {}!", name)
}

构建

wasm-pack build --target web

在 HTML 中使用

<script type="module">
  import init, { add, greet } from './pkg/my_wasm.js';

  await init();
  console.log(add(1, 2));        // 3
  console.log(greet('World'));   // "Hello, World!"
</script>

Cheatsheet

工具用途
wasm-pack构建 Rust 到 WASM
wasm-bindgenJS/Rust 绑定
wasm-opt优化 WASM
wabtWASM 工具包 (wat2wasm)
emscriptenC/C++ 到 WASM

Gotchas

手动加载 WASM

// 获取并实例化
const response = await fetch('module.wasm');
const bytes = await response.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes);

// 调用导出函数
const result = instance.exports.add(1, 2);

从 Rust 操作 DOM

use wasm_bindgen::prelude::*;
use web_sys::{Document, Element, Window};

#[wasm_bindgen]
pub fn update_dom() {
    let window: Window = web_sys::window().unwrap();
    let document: Document = window.document().unwrap();

    let element: Element = document.get_element_by_id("output").unwrap();
    element.set_inner_html("从 Rust 更新!");
}
# Cargo.toml
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = ["Window", "Document", "Element"] }

在 Rust 中使用 JavaScript 回调

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn process_with_callback(callback: &js_sys::Function) {
    let result = JsValue::from(42);
    callback.call1(&JsValue::NULL, &result).unwrap();
}
import { process_with_callback } from './pkg/my_wasm.js';

process_with_callback((result) => {
  console.log('收到结果:', result);
});

异步函数

use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, Response};

#[wasm_bindgen]
pub async fn fetch_data(url: String) -> Result<JsValue, JsValue> {
    let window = web_sys::window().unwrap();
    let resp_value = JsFuture::from(window.fetch_with_str(&url)).await?;
    let resp: Response = resp_value.dyn_into()?;
    let json = JsFuture::from(resp.json()?).await?;
    Ok(json)
}

内存管理

use wasm_bindgen::prelude::*;

// 高效传递大数据
#[wasm_bindgen]
pub fn sum_array(data: &[i32]) -> i32 {
    data.iter().sum()
}

// 返回 Vec(复制到 JS)
#[wasm_bindgen]
pub fn generate_data(size: usize) -> Vec<i32> {
    (0..size as i32).collect()
}
// 传递 TypedArray
const data = new Int32Array([1, 2, 3, 4, 5]);
const sum = sum_array(data);

配合打包工具使用 (webpack/vite)

// Vite
import init, { add } from './pkg/my_wasm.js?init';

const wasm = await init();
console.log(add(1, 2));
// Webpack
import * as wasm from './pkg/my_wasm.js';

wasm.default().then(() => {
  console.log(wasm.add(1, 2));
});

使用 Emscripten 编译 C/C++

# 安装
brew install emscripten  # 或 apt-get

# 编译
emcc hello.c -o hello.js -s WASM=1 -s EXPORTED_FUNCTIONS='["_add"]'
// hello.c
int add(int a, int b) {
    return a + b;
}

Next Steps