Node.js、浏览器和 Cloudflare Workers 的wasm引入问题

最近在鼓捣 WebAssembly,这玩意没啥神秘的,可以看出一坨二进制的 .js 库文件

它本身的export和调用方式是统一的,但如今天我才知道,在不同 JavaScript 运行环境加载 .wasm 方式并不完全一样。

例如 Cloudflare Workers 可以直接:

import wasm from "./simple.wasm";

const { instance } = await WebAssembly.instantiate(wasm, imports);

Cloudflare 会把 .wasm 模块作为 WebAssembly.Module 提供给代码。

浏览器通常则是通过 fetch() 获取 Wasm,再使用 WebAssembly.instantiateStreaming():

const { instance } = await WebAssembly.instantiateStreaming(
  fetch("./simple.wasm"),
  imports,
);

Node.js 则通过文件系统读取 .wasm 二进制,然后初始化。

.instantiate() 第一参数必须是 typed array 或者 ArrayBuffer

我自己写了个 Wasm 库,AI 为 Node、浏览器、Cloudflare Workers 分别写一套初始化代码,8个 .mjs 文件。给我搞懵了。

提了个简化思路:让初始化函数只接受两种东西:string 表示路径,或者cf那种 WebAssembly.Module 对象

然后搞的时候发现,为了兼容 nodejs,必须 import "node:fs" ,才能读 .wasm 文件嘛

然后逆天的 js 引入必须在 top-level ,在 cf 和浏览器侧就必挂。你 typeof process 提前判断一下环境都不行。

不过AI这时候想到一个聪明办法

if (process?.getBuiltinModule) {
  const fs = process.getBuiltinModule("fs");

  const bytes = await fs.promises.readFile(source);
  return WebAssembly.compile(bytes);
}

卧槽,有这么好的办法,你为啥不早点告诉我。

于是使用方式统一成:

Cloudflare Workers

import wasm from "./simple.wasm";
import { init } from "./foo.mjs";

const foo = await init(wasm);

Browser / Node.js

import { init } from "./foo.mjs";

const foo = await init("./simple.wasm");

Comments