GoでWebAssembly試してみた
前書き
これから作ろうとするものでGoでWebAssemblyできると都合がよいので勉強をした。
その備忘録としてここに記す。
環境構築
言語等の必要なもののインストール
GoでWebAssemblyするならtinygoということでインストールを試みた。公式サイトにscoopを使うよう指示されたがscoopがインストールされていなかったためscoopのインストールから行った。
以下のコマンドでscoopのインストールを行う。
Set-ExecutionPolicy RemoteSigned -scope CurrentUser
invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
scoopをインストールしたら下記のものをscoopを使いインストールした。
- Go
- TinyGo
- binaryen
VScodeの設定
.vscode
フォルダー内のsettings.json
を以下のように記述。
{
"go.toolsEnvVars": {
"GOOS": "js",
"GOARCH": "wasm"
},
"go.testEnvVars": {
"GOARCH":"wasm",
"GOOS":"js",
}
}
この記述がないとVScodeにのちに登場するsyscall/js
がインポートできないよって怒られてしまう。(VScodeに怒られててもビルドはできるしちゃんと動く)
GoでWebAssembly
GoでWebAssemblyをやるにあたりサーバーの実装も必要であった。様々な記事を見るとgoexec
を使ってサーバーを立ち上げてる人も多くいたが、今回は公式の通り自分で静的ファイルを配信するサーバーを立てることとした。
JavaScriptに読み込ませるGoの関数を作成
とりあえずコンソールにHello Wasm!!
と表示する関数を実装。
以下がその関数。
func HalloWasm(this js.Value, args []js.Value) interface{} {
println("Hello Wasm!!")
return nil
}
そして以下の関数がjavascriptのWindowオブジェクトに関数をセットする。
func registerCallbacks() {
js.Global().Set("HelloWasm", js.FuncOf(HalloWasm))
}
main関数は以下のようになっている。
func main() {
c := make(chan struct{})
registerCallbacks()
<-c
}
buildするGoのコードが書けたら以下のコマンドでビルドする。
このコマンドでビルドしてwasm.wasmを吐き出したらひとまずOK。
tinygo build -o wasm.wasm -target wasm ./main.go
JavaScriptが読み込むために必要なwasm_exec.jsの用意
こちら(wasm_exec.js)はtinygoをインストールしたときについてくるためそれを流用すればよいと書いてあったので盲目的に流用した。このファイルがないとwasmバイナリを読み込むことができない。
C:\Users\(username)\scoop\apps\tinygo\0.26.0\targets
の中にwasm_exec.jsという名前のファイルがあった。これはネットにあるやつではなく、自身のローカルにあるものを使ったほうがよい。というよりtinygoのバージョンによって実装が異なるらしいのでそうしないとほとんど動かないのでは。
index.htmlの用意
まずはwasm_exec.js
を読み込む。
<script src="wasm_exec.js"></script>
次にwasmバイナリーを読み込み、Goで実装した関数を利用できるようにする。
if文で分岐して二つあるのはブラウザによって読み込む際使う関数が違うかららしい。
const go = new Go();
const WASM_URL = 'wasm.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
また画面にボタンを用意した。ボタンのクリックイベントにGoで実装した関数を登録し、ボタンを押したときコンソールにHello wasm!!
と表示されれば晴れて成功である。
<button onClick="HelloWasm()" id="addButton">console print</button>
結果
画像の各部分が小さくて申し訳ないがこんな感じで表示できたのでこれで成功だ。
感想
ネット上にあまり情報がないのではと思ったし、少なかったがどれも的確につまずいた所を補填してくれたため少ないことをデメリットに感じなかった。おそらくデメリットに感じることがあるのはこのさき複雑なことをしようとしたときなんだろうなと思う。
ここからReactとGoでWebAssemblyの共存をさせたいのでまたそれは別の記事を作成するだろうなと思う。
参考文献
WebAssemblyにかかわるやつ
環境構築にかかわるやつ
Discussion