我使用 Go 创建了一个测试 WASM 程序。在程序的 main 中,它向“全局”添加了一个 API,并在通道上等待以避免退出。它类似于您可以在互联网上任何地方找到的典型的 hello-world Go WASM。
我的测试 WASM 程序在浏览器中运行良好,但是,我希望使用 Node.js 运行它并调用 API。如果可能的话,我会基于它创建一些自动化测试。
我尝试了很多方法,但就是无法让它与 Node.js 一起工作。问题是,在 Node.js 中,在“全局”中找不到 API。如何在 Node.js 中运行 GO WASM 程序(带有导出的 API)?
(如果您需要更多详细信息,请告诉我)
谢谢!
更多详情:
--- Go 这边(伪代码)---
func main() {
fmt.Println("My Web Assembly")
js.Global().Set("myEcho", myEcho())
<-make(chan bool)
}
func myEcho() js.Func {
return js.FuncOf(func(this js.Value, apiArgs []js.Value) any {
for arg := range(apiArgs) {
fmt.Println(arg.String())
}
}
}
// build: GOOS=js GOARCH=wasm go build -o myecho.wasm path/to/the/package
--- 在浏览器端---
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<p><pre style="font-family:courier;" id="my-canvas"/></p>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
WebAssembly.instantiateStreaming(fetch("myecho.wasm"), go.importObject).then((result) => {
go.run(result.instance);
}).then(_ => {
// it also works without "window."
document.getElementById("my-canvas").innerHTML = window.myEcho("hello", "ahoj", "ciao");
})
})
</script>
</body>
</html>
--- 在 Node.js 方面 ---
globalThis.require = require;
globalThis.fs = require("fs");
globalThis.TextEncoder = require("util").TextEncoder;
globalThis.TextDecoder = require("util").TextDecoder;
globalThis.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
const crypto = require("crypto");
globalThis.crypto = {
getRandomValues(b) {
crypto.randomFillSync(b);
},
};
require("./wasm_exec");
const go = new Go();
go.argv = process.argv.slice(2);
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
go.exit = process.exit;
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
go.run(result.instance);
}).then(_ => {
console.log(go.exports.myEcho("hello", "ahoj", "ciao"));
}).catch((err) => {
console.error(err);
process.exit(1);
});
这个伪代码代表了我真实代码的99%内容(只删除了业务相关的细节)。问题是我不仅需要通过 Node.js 运行 wasm 程序(myecho.wasm),还需要调用“api”(myEcho),并且需要向其传递参数并接收返回值,因为我想为这些“api”创建自动化测试。借助 Node.js,我可以在命令行环境中启动测试 js 脚本并验证输出。对于这种情况,浏览器不是一个方便的工具。
通过
node wasm_exec.js myecho.wasm
运行程序对于我的情况来说还不够。
如果能够了解有关您的环境以及您实际想要做什么的更多详细信息,那就太好了。您可以发布代码本身、编译命令以及所有涉及的工具的版本。
尝试在没有这些细节的情况下回答问题:
Go WASM 是非常面向浏览器的,因为 go 编译器需要 wasm_exec.js 中的glue js 来运行。 Nodejs 不应该有这个问题,并且以下命令应该可以工作:
node wasm_exec.js main.wasm
其中
wasm_exec.js
是随 go
发行版附带的粘合代码,通常可以在 $(go env GOROOT)/misc/wasm/wasm_exec.js
中找到,而 main.wasm
是编译后的代码。如果失败,您也可以发布输出。
还有另一种方法可以绕过
wasm_exec.js
将 go 代码编译为 wasm,这种方法是使用 TinyGo 编译器输出支持 wasi 的代码。您可以尝试按照他们的说明来编译您的代码。
对于示例:
tinygo build -target=wasi -o main.wasm main.go
您可以为 example 构建一个 javascript 文件
wasi.js
:
"use strict";
const fs = require("fs");
const { WASI } = require("wasi");
const wasi = new WASI();
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
(async () => {
const wasm = await WebAssembly.compile(
fs.readFileSync("./main.wasm")
);
const instance = await WebAssembly.instantiate(wasm, importObject);
wasi.start(instance);
})();
最新版本的
node
有实验性的wasi
支持:
node --experimental-wasi-unstable-preview1 wasi.js
这些通常是您使用 Go 和 WASM 尝试的事情,但如果没有更多细节,很难说出到底什么不起作用。
经过一番挣扎,我发现原因比我想象的要简单。
我无法在 Node.js 中获取导出的 API 函数,因为当我尝试调用它们时,API 尚未导出!
当 wasm 程序加载并启动时,它与调用者程序(Node 中运行的 js)并行运行。
WebAssembly.instantiate(...).then(...go.run(result.instance)...).then(/*HERE!*/)
“HERE”处的代码执行得太早,wasm 程序的
main()
尚未完成 API 导出。
当我将节点脚本更改为以下内容时,它起作用了:
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
go.run(result.instance);
}).then(_ => {
let retry = setInterval(function () {
if (typeof(go.exports.myEcho) != "function") {
return;
}
console.log(go.exports.myEcho("hello", "ahoj", "ciao"));
clearInterval(retry);
}, 500);
}).catch((err) => {
console.error(err);
process.exit(1);
});
(仅包含变更部分)
我知道这似乎不是一个完美的解决方案,但至少它证明了我对根本原因的猜测是正确的。
但是...为什么它没有在浏览器中发生? 叹息...