年末闲着没事折腾 Cloudflare Workers,发现这玩意儿的运行时对 wasm 模块进行了一些限制,导致用起来很蛋疼,没刻意适配过的库基本上是没办法直接使用的。
WASM 的正常加载方法
正常情况下,WASM 模块的加载方法可以分为动态和静态两类
动态加载,也就是直接从二进制数据(流)中构造一个 WebAssembly.Instance
。这种方法非常灵活,可以把自己伪装成正常的 js 模块,不需要打包工具的特殊支持,因此使用最广。
1
2
3
4
5
6
7
8
9
10
11
|
const importObject = {
imports: {
imported_func(arg) {
console.log(arg);
},
},
};
WebAssembly.instantiateStreaming(fetch("simple.wasm"), importObject).then(
(obj) => obj.instance.exports.exported_func(),
);
|
静态加载,则是把 .wasm 作为一个 es 模块直接 import。实际很少有 js 运行时原生支持这种用法,连打包工具都基本只有 webpack 完整支持。
1
|
import { imported_func } from './simple.wasm'
|
workerd 里的 WASM
前面说「几乎」所有 js 运行时都只能动态加载 wasm,不能静态加载。
但有一个运行时不一样——那就是 CF Workers 的运行时 workerd,这玩意儿(自称)出于安全原因,不支持动态加载,只支持静态加载!(CF Workers 几乎限制了所有动态执行 js/wasm 代码的手段)
不仅如此,坑爹 CF 在 Rust Workers 文档里还提了一嘴(TM 为啥放在 Rust 部分的文档里),同样出于安全和性能原因,workerd 中导入 wasm 会获得一个 WebAssembly.Module
而不是 WebAssembly.Instance
,这导致某些 target 下生成的入口点代码是没法正常运行的。
1
2
3
4
5
6
7
|
// jieba-wasm/pkg/bundler/jieba_rs_wasm.js
import * as wasm from "./jieba_rs_wasm_bg.wasm";
export * from "./jieba_rs_wasm_bg.js";
import { __wbg_set_wasm } from "./jieba_rs_wasm_bg.js";
// __wbg_set_wasm 期望 wasm 是一个 WebAssembly.Instance
// 但 workerd 里会传入一个 WebAssembly.Module,于是 gg
__wbg_set_wasm(wasm);
|
咋办呢,只能自己改一下这部分的代码了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// jieba.js
import * as imports from '../node_modules/jieba-wasm/pkg/bundler/jieba_rs_wasm_bg.js';
import wkmod from '../node_modules/jieba-wasm/pkg/bundler/jieba_rs_wasm_bg.wasm';
import * as nodemod from '../node_modules/jieba-wasm/pkg/bundler/jieba_rs_wasm_bg.wasm';
// 根据运行环境来判断是否可以直接传入
if (typeof process !== 'undefined' && process.release.name === 'node') {
imports.__wbg_set_wasm(nodemod);
} else {
const instance = new WebAssembly.Instance(wkmod, {
'./jieba_rs_wasm_bg.js': imports,
});
imports.__wbg_set_wasm(instance.exports);
}
export * from '../node_modules/jieba-wasm/pkg/bundler/jieba_rs_wasm_bg.js';
|
上面的写法看起来非常炸裂,这主要是因为 jieba-wasm 并没有导出其 .wasm 模块,只能通过这种方法来 import 了……
不过我后面发现其实有更简单的方法——web target 下生成的初始化函数支持手动传入模块,于是就可以这样导入:
1
2
3
4
5
|
import init, { cut } from 'jieba-wasm/web';
import wasm from '../../node_modules/jieba-wasm/pkg/web/jieba_rs_wasm_bg.wasm';
await init(wasm);
...
|
多了个 init 函数,不过整体还是简洁不少。
参考
文章作者
上次更新
2025-01-03
许可协议
知识共享署名-非商业性使用 4.0 国际许可协议