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

pgccezyw  于 2023-02-10  发布在  Go
关注(0)|答案(2)|浏览(592)

我用Go语言创建了一个测试WASM程序。在程序的main中,它为"全局"添加了一个API,并等待一个通道以避免退出。它类似于典型的hello世界Go WASM,你可以在互联网上的任何地方找到。
我的测试WASM程序在浏览器中运行良好,不过,我希望运行它并使用Node.js调用API。如果可能的话,我将基于它创建一些自动化测试。
我尝试了很多方法,但我就是不能让它在Node.js中工作。问题是,在Node.js中,API无法在"全局"中找到。我如何在Node.js中运行GO WASM程序(带有导出的API)?
(Let我知道如果你需要更多的细节)
谢谢!
更多详情:

  • --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%内容(只删除了业务相关的细节)。问题是我不仅需要运行WASM程序(myecho.wasm),但我还需要调用"api"(myEcho),我需要向它传递参数并接收返回值,因为我想为这些"api"创建自动化测试。我可以在命令行环境中启动测试js脚本并验证输出结果,浏览器对这种情况来说不是一个方便的工具。
node wasm_exec.js myecho.wasm运行程序不足以解决我的问题。

eyh26e7m

eyh26e7m1#

如果能知道更多关于你的环境的细节以及你实际上想做什么,那就太好了。你可以发布代码本身、编译命令和所有涉及到的工具的版本。
尝试回答问题而不考虑这些细节:
Go WASM非常面向浏览器,因为Go编译器需要wasm_exec.js中的glue js来运行。Nodejs应该不会有问题,下面的command应该可以工作:

node wasm_exec.js main.wasm

其中wasm_exec.jsgo发行版附带的粘合代码,通常可以在$(go env GOROOT)/misc/wasm/wasm_exec.js中找到,main.wasm是您的编译代码。如果失败,您也可以发布输出。
还有一种方法可以绕过wasm_exec.js将go代码编译成wasm,那就是使用TinyGo编译器输出支持wasi的代码,你可以尝试按照他们的指导编译你的代码。
对于example

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中尝试的东西,但是如果没有进一步的细节,很难说到底是什么不起作用。

jhiyze9q

jhiyze9q2#

经过一番挣扎,我注意到原因比我想象的要简单。

我无法在Node.js中获取导出的API函数,原因很简单,当我试图调用它们时,API尚未导出!

当加载并启动wasm程序时,它与调用程序(在Node中运行的js)并行运行。
WebAssembly.instantiate(...).then(...go.run(result.instance)...).then(/*HERE!*/)
“HERE”处的代码执行过早,wasm程序的main()尚未完成API导出。
当我将Node脚本更改为以下内容时,它工作正常:

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);
});

(only包括变更部分)
我知道这似乎不是一个完美的解决方案,但至少它证明了我对根本原因的猜测是正确的。
但是...为什么它没有发生在浏览器?* 叹息... *

相关问题