🤖

GoでWebAssembly試してみた

2023/01/08に公開

前書き

これから作ろうとするものでGoでWebAssemblyできると都合がよいので勉強をした。
その備忘録としてここに記す。

https://github.com/ystsbry/go-wasm-sample

環境構築

言語等の必要なもののインストール

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にかかわるやつ

https://tinygo.org/docs/guides/webassembly/
https://github.com/ludwig125/githubpages
https://qiita.com/KEINOS/items/d3b7123a46dcf7b722e9

環境構築にかかわるやつ

https://qiita.com/rhene/items/d8a0c0c7d637904e14da
https://tinygo.org/getting-started/install/windows/

Discussion