如何使用 Node.js 运行 Go WASM 程序?

问题描述 投票:0回答:2

我使用 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
运行程序对于我的情况来说还不够。

node.js go webassembly go-wasm
2个回答
1
投票

如果能够了解有关您的环境以及您实际想要做什么的更多详细信息,那就太好了。您可以发布代码本身、编译命令以及所有涉及的工具的版本。

尝试在没有这些细节的情况下回答问题:

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 尝试的事情,但如果没有更多细节,很难说出到底什么不起作用。


0
投票

经过一番挣扎,我发现原因比我想象的要简单。

我无法在 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);
});

(仅包含变更部分)

我知道这似乎不是一个完美的解决方案,但至少它证明了我对根本原因的猜测是正确的。

但是...为什么它没有在浏览器中发生? 叹息...

© www.soinside.com 2019 - 2024. All rights reserved.