本页内容
WebAssembly
WebAssembly (Wasm) 旨在与 JavaScript 一起使用,以加速关键应用程序组件,它可以比 JavaScript 具有更高且更一致的执行速度——类似于 C、C++ 或 Rust。WebAssembly Deno 可以使用与 浏览器提供的接口执行 WebAssembly 模块,并通过将它们作为模块导入。
Wasm 模块 跳转到标题
从 Deno 2.1 开始,WebAssembly 模块可以被导入,并且它们的使用会进行类型检查。
假设我们有一个 WebAssembly 文本格式文件,它导出一个 add
函数,该函数将两个数字相加并返回结果
(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
然后通过 import 语句使用此 WebAssembly 模块
import { add } from "./add.wasm";
console.log(add(1, 2));
> deno run main.ts
3
类型检查 跳转到标题
Deno 理解 Wasm 模块的导出,并对其使用进行类型检查。如果在之前的示例中我们错误地调用了 add
函数,我们将看到类型检查错误。
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
导出
(module
(import "./time.ts" "getTimeInSeconds" (func $get_time (result i32)))
(func (export "getValue") (result i32)
call $get_time
)
)
export function getTimeInSeconds() {
return Date.now() / 1000;
}
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" 说明符导入。
(module
(import "env" "get_time_in_seconds" (func $get_time (result i32)))
(func (export "getValue") (result i32)
call $get_time
)
)
function getTimeInSeconds() {
return Date.now() / 1000;
}
export { getTimeInSeconds as get_time_in_seconds };
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 中通过 导入映射映射说明符,使其工作非常简单
{
"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,需要执行以下步骤
- 获取二进制文件(通常以
.wasm
文件的形式,尽管我们现在使用的是简单的字节数组) - 将二进制文件编译成
WebAssembly.Module
对象 - 实例化 WebAssembly 模块
WebAssembly 是一种二进制数据格式,不适合人类阅读,也不适合手工编写。您的 .wasm
文件应由诸如 Rust、Go 或 AssemblyScript 等语言的编译器生成。
例如,一个编译为上述字节的 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 crates 很有用。web_sys
包含绑定到 Deno 中可用的大多数 Web API,而 js_sys
提供绑定到 JavaScript 的标准内置对象。
优化 跳转到标题
对于生产版本,您可以对 WebAssembly 二进制文件执行优化。如果您通过网络提供二进制文件,那么针对大小进行优化可能会产生实际差异。如果您主要在服务器上执行 WebAssembly 以执行计算密集型任务,那么针对速度进行优化可能是有益的。您可以在 此处找到有关优化(生产)构建的良好指南。此外,rust-wasm 组列出了可用于优化和操作 WebAssembly 二进制文件的工具。