deno.com
本页内容

WebAssembly

WebAssembly (Wasm) 旨在与 JavaScript 协同使用,以加速关键应用程序组件,它比 JavaScript 具有更高且更一致的执行速度——类似于 C、C++ 或 Rust。Deno 可以使用与浏览器提供的相同接口来执行 WebAssembly 模块,也可以通过将它们作为模块导入来执行。

Wasm 模块 跳转到标题

从 Deno 2.1 开始,可以导入 WebAssembly 模块,并且其使用会进行类型检查。

假设我们有一个WebAssembly 文本格式文件,它导出一个 `add` 函数,该函数添加两个数字并返回结果

add.wat
(module
  (func (export "add") (param $a i32) (param $b i32) (result i32)
    local.get $a
    local.get $b
    i32.add
  )
)

我们可以通过 wat2wasm 将其编译为 `add.wasm`

wat2wasm add.wat

然后通过导入语句使用此 WebAssembly 模块

main.ts
import { add } from "./add.wasm";

console.log(add(1, 2));
> deno run main.ts
3

类型检查 跳转到标题

Deno 能够理解 Wasm 模块的导出并对其使用进行类型检查。如果在上一个示例中错误地调用 `add` 函数,我们将看到一个类型检查错误。

main.ts
import { add } from "./add.wasm";

console.log(add(1, ""));
> deno check main.ts   
Check file:///.../main.ts
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'number'.
console.log(add(1, ""));
                   ~~
    at file:///.../main.ts:3:20

导入 跳转到标题

与 JavaScript 类似,Wasm 模块也可以导入其他模块。

例如,我们可以创建一个 Wasm 模块,它导入 `./values.js` 说明符并调用 `getValue` 导出

toolkit.wat
(module
  (import "./time.ts" "getTimeInSeconds" (func $get_time (result i32)))

  (func (export "getValue") (result i32)
    call $get_time
  )
)
time.ts
export function getTimeInSeconds() {
  return Date.now() / 1000;
}
main.ts
import { getValue } from "./toolkit.wasm";

console.log(getValue());

现在运行

> wat2wasm toolkit.wat
> deno run main.ts
1732147633
V:\scratch
> deno run main.ts
1732147637

覆盖导入说明符 跳转到标题

通常,Wasm 模块不使用相对说明符来方便导入其他 JavaScript 模块。假设我们有与之前类似的设置,但请注意 Wasm 模块是通过“env”说明符导入的。

toolkit.wat
(module
  (import "env" "get_time_in_seconds" (func $get_time (result i32)))

  (func (export "getValue") (result i32)
    call $get_time
  )
)
env.ts
function getTimeInSeconds() {
  return Date.now() / 1000;
}

export { getTimeInSeconds as get_time_in_seconds };
main.ts
import { getValue } from "./toolkit.wasm";

console.log(getValue());
> wat2wasm toolkit.wat
> deno run main.ts
error: Relative import path "env" not prefixed with / or ./ or ../
    at file:///.../toolkit.wasm

这不是很方便,因为我们希望它导入 `./env.ts`。

幸运的是,通过 导入映射 在 `deno.json` 中映射说明符,使其工作起来非常简单

deno.json
{
  "imports": {
    "env": "./env.ts"
  }
}

现在可以了

> deno run main.ts
1732148355

通过 WebAssembly API 使用 WebAssembly 跳转到标题

要在 Deno 中运行 WebAssembly,您只需要一个 Wasm 模块。以下模块导出一个 `main` 函数,该函数在调用时只返回 `42`

// deno-fmt-ignore
const wasmCode = new Uint8Array([
  0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127,
  3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0,
  5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145,
  128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97,
  105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0,
  65, 42, 11
]);

const wasmModule = new WebAssembly.Module(wasmCode);

const wasmInstance = new WebAssembly.Instance(wasmModule);

const main = wasmInstance.exports.main as CallableFunction;
console.log(main().toString());

为了通过 WebAssembly API 加载 WebAssembly,需要执行以下步骤

  1. 获取二进制文件(通常是 `.wasm` 文件的形式,尽管我们现在使用的是简单的字节数组)
  2. 将二进制文件编译为 `WebAssembly.Module` 对象
  3. 实例化 WebAssembly 模块

WebAssembly 是一种二进制数据格式,不适合人类阅读,也不适合手动编写。您的 `.wasm` 文件应由 RustGoAssemblyScript 等语言的编译器生成。

例如,一个编译成上述字节的 Rust 程序会是这样的

#[no_mangle]
pub fn main() -> u32 { // u32 stands for an unsigned integer using 32 bits of memory.
  42
}

使用流式 WebAssembly API 跳转到标题

获取、编译和实例化 WebAssembly 模块最有效的方法是使用 WebAssembly API 的流式变体。例如,您可以将 `instantiateStreaming` 与 `fetch` 结合使用,一次性完成所有这三个步骤

const { instance, module } = await WebAssembly.instantiateStreaming(
  fetch("https://wpt.live/wasm/incrementer.wasm"),
);

const increment = instance.exports.increment as (input: number) => number;
console.log(increment(41));

请注意,`.wasm` 文件必须使用 `application/wasm` MIME 类型提供。如果您想在实例化之前对模块进行额外处理,您可以改为使用 `compileStreaming`

const module = await WebAssembly.compileStreaming(
  fetch("https://wpt.live/wasm/incrementer.wasm"),
);

/* do some more stuff */

const instance = await WebAssembly.instantiate(module);
instance.exports.increment as (input: number) => number;

如果由于某种原因无法使用流式方法,您可以退回到效率较低的 `compile``instantiate` 方法。

要更深入地了解流式方法为何更高效,请查看这篇博文

WebAssembly API 跳转到标题

有关 WebAssembly API 所有部分的更多信息,可以在 Deno 参考指南MDN 上找到。

处理非数值类型 跳转到标题

本文档中的代码示例仅在 WebAssembly 模块中使用了数值类型。要运行包含更复杂类型(例如字符串或类)的 WebAssembly,您需要使用生成 JavaScript 与用于编译到 WebAssembly 的语言之间类型绑定的工具。

如何在 JavaScript 和 Rust 之间创建类型绑定、将其编译为二进制文件并从 JavaScript 程序中调用它的示例可以在 MDN 上找到。

如果您计划在 Rust+WebAssembly 中大量使用 Web API,您可能会发现 `web_sys``js_sys` 这两个 Rust crate 很有用。`web_sys` 包含 Deno 中可用的大多数 Web API 的绑定,而 `js_sys` 提供 JavaScript 标准内置对象的绑定。

在 Deno 中使用 wasmbuild 处理 Rust WebAssembly 跳转到标题

wasmbuild 是一个官方的 Deno 工具,它简化了在 Deno 项目中使用 Rust 和 WebAssembly 的工作。它自动化了将 Rust 代码编译为 WebAssembly 并生成 TypeScript 绑定的过程,使得从 JavaScript 调用 Rust 函数变得容易。

wasmbuild 为您的 Rust 函数生成 TypeScript 定义,提供完整的类型检查。生成的 JavaScript 可以与 esbuild 等打包工具一起使用。生成的文件可以直接提交到源代码管理以方便部署。

优化 跳转到标题

对于生产构建,您可以对 WebAssembly 二进制文件进行优化。如果您通过网络提供二进制文件,那么针对大小进行优化可以带来显著差异。如果您主要在服务器上执行 WebAssembly 以执行计算密集型任务,那么针对速度进行优化可能会有所帮助。您可以在这里找到关于优化(生产)构建的良好指南。此外,`rust-wasm` 小组有一系列可用于优化和操作 WebAssembly 二进制文件的工具。

您找到所需内容了吗?

隐私政策