2022年9月11日 • ☕️ 3 min read

JavaScriptでGoの関数を呼び出す方法を紹介します。

Go側

簡単な関数を作ってみましょう。

フォルダを作ってGoのmain関数を作る

Copy
# フォルダを作る
mkdir js-call-wasm

# 初期化
go mod init example.com/js-call-wasm

VSCodeで開いて、main.goを作ります。

main.go
Copy
package main

import "syscall/js"

func add(a float32, b float32) float32 {
	return a + b
}

func main() {
	js.Global().Set("add", js.FuncOf(add))
}

JavaScriptを呼び出す及び呼び出されるために、こちらsyscall/jsを使っています。一番簡単な方法はglobalにadd関数を追加することにします(js.Global().Set)。

VSCodeの赤線問題

VSCodeのGoプラグインがうまくGOOSGOARCHを識別できないため、syscall/jsに赤線が表示されます。 エラーメッセージはこちらです。

vscode_plugin_err.png

Copy
could not import syscall/js (cannot find package "syscall/js" in any of 
	/usr/local/go/src/syscall/js

解決方法は.vscode/settings.jsonに下記の設定の追加することです。

.vscode/settings.json
Copy
{
  "gopls": {
    "build.env": {
      "GOOS": "js",
      "GOARCH": "wasm"
    }
  }
}

そしてReload Windowで赤線が消えてしまいます。

Go関数を改造

Goのオリジナルの関数を直接呼び出せません。WebAssemblyのため下記のように改造する必要があります。

main.go
Copy
package main

import "syscall/js"

// JS用に改造func add(this js.Value, inputs []js.Value) interface{} {	return inputs[0].Float() + inputs[1].Float()}
func main() {
	js.Global().Set("add", js.FuncOf(add))
}

JavaScriptから呼び出せる関数の1つ目は固定でthis js.Valueでいいで、2つ目はパラメータ関数のパラメータとなります。 足し算の関数なので、パラメータが2つで、配列になっています。

interface{}はTypeScriptのanyというものだと考えればいいです。

さらにmainの部分を改造

Goのmain関数が受け付けられるように、さらに改造する必要があります。main関数が終了にならないように、クローズしないチャネルをオープンします。

main.go
Copy
package main

import "syscall/js"

// JS用に改造
func add(this js.Value, inputs []js.Value) interface{} {
	return inputs[0].Float() + inputs[1].Float()
}

// チャネルオープンfunc main() {
  c := make(chan int)	js.Global().Set("add", js.FuncOf(add))
  <-c}

ビルド

コンパイルサイズを抑えるために、tinygoを使います。MacOSのインストール方法はこちらです。

Copy
brew tap tinygo-org/tools && brew install tinygo

そしてコンパイルして生成されたJavaScriptもコピーします。

Copy
tinygo build -target wasm -o main.wasm -no-debug

# 初回目のみ、2回目から上のコンパイルコマンドを実行するだけでOK
cp "$(tinygo env TINYGOROOT)/targets/wasm_exec.js" .

これでGo側の準備は完了しました。

html側

同じフォルダにindex.htmlを作りましょう。定型文なのでそのまま貼り付けます。

index.html
Copy
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="wasm_exec.js"></script>  <script>    async function init() {      const go = new Go();      const result = await WebAssembly.instantiateStreaming(        fetch("main.wasm"),        go.importObject,      );      go.run(result.instance);      console.log(add(2, 4))    }    init()  </script></head>

<body>

</body>

</html>

ローカルでホスティング

VSCodeのプラグインLive Serverを使ったらとても簡単にできるのでおすすめです。

vscode_plugin_liveserver

そして、index.htmlを開いて、下のステータスバーから Go Liveを押したらブラウザで確認できます。

statusbar_golive.png

URLはhttp://127.0.0.1:5500/index.htmlとなります。

DevToolsを開いたら実行できたことを確認できるでしょう。

devtools_output.png

完了

やはりまだちょっと面倒でしょう。一番気になるのはGoのwasmファイルのサイズですね。次回Rustを使って同じことをやってみたいです。


ThunderMiracle

Blog part of ThunderMiracle.com