Go x WebAssembly ってどんな？



この記事は [Wano Group Advent Calendar 2019](https://qiita.com/advent-calendar/2019/wano-group) の12日目の記事となります。

Advent Calendar もそろそろ折り返しですね🎅🎁

## GoとWebAssembly
WebAssemblyとは何ぞって話は詳しくはしませんが、ブラウザが読めるアセンブリのコード形式のことです。（[詳しくはここ](https://www.w3.org/TR/wasm-core-1/)）
特徴としてjavascriptに比べ高速という点が挙げられ、jsの補完的な立ち位置で、[モダンブラウザは概ねサポートしています。](https://caniuse.com/#feat=wasm)

Goではv1.11よりWebAssemblyがサポートされるようになり、進行形で様々な改善がされているみたいです。
[Official](https://github.com/golang/go/wiki/WebAssembly)に色々書いてあります。

ということで勉強がてらGo x WebAssemblyをかなり簡単にですが触ってみて、所感を書こうと思います。

## Hello Worldしてみる
[公式のGetting Started](https://github.com/golang/go/wiki/WebAssembly#getting-started) を参考にやってみます。

### 1. WebAssemblyバイナリ

スクリプトは特に何も意識することないですね。 これをWebAssembly形式にビルドします。
さすがビルド速いです

```go:test.go
package main

import "fmt"

func main() {
	fmt.Println("Hello, WebAssembly!")
}
```

```
$ GOOS=js GOARCH=wasm go build -o test.wasm
```

### 2. html・jsファイル
すでにGoのインストール時にサンプルがあるのでそれをコピーして使用します。


```
$ cp /usr/local/Cellar/go/1.12.5/libexec/misc/wasm/wasm_exec.{js,html} ./
```

#### 3. Webサーバー起動

Webサーバーを立てておきます。

```go:server/server.go
package main

import "net/http"

func main() {
	http.ListenAndServe(":8080", http.FileServer(http.Dir("../")))
}
```

```
$ go run server/server.go
```

#### 4. ブラウザで確認
`Run` ボタンをクリックすると以下のようにコンソールに出力されます

<img width="597" alt="スクリーンショット 2019-12-09 0.36.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/1e122ea6-4ea3-9a05-f79f-220da89ba98c.png">

ディレクトリ構成は以下になります。

```
|--server
| |--server.go
|--test.go
|--test.wasm
|--wasm_exec.html
|--wasm_exec.js
```

`test.wasm` を実行するためのスクリプトが `wasm_exec.js` で、それと `test.wasm` を `wasm_exec.html` が呼んでいるという流れになります。 

## JSで簡単な操作をしてみる
👆のファイルを少々手直しして、JSっぽいことをしてみます。

ボタンクリック時に背景色を変更させます。





```html:sample.html
<!doctype html>
<html>

<body>

<script src="wasm_exec.js"></script>
<script>
    const go = new Go();
    let mod, inst;
    WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(async (result) => {
        mod = result.module;
        inst = result.instance;

        await go.run(inst);
        inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
    }).catch((err) => {
        console.error(err);
    });
</script>

<input type="text" id="colorCodeInput">
<button type="submit" id="colorChangeButton">背景色を変更する</button>
</body>

</html>
```

👇ゴリッゴリにjs叩いてます

```go:sample.go
package main

import (
	"syscall/js"
)

func main() {
	document := js.Global().Get("document")

	body := document.Call("getElementsByTagName", "body").Index(0)
	input := document.Call("getElementById", "colorCodeInput")

	cb := js.FuncOf(func(this js.Value, args []js.Value) interface{}{
		body.Get("style").Set("backgroundColor", input.Get("value").String())
		return nil
	})
	document.Call("getElementById", "colorChangeButton").Call("addEventListener", "click", cb)

	<-make(chan struct{}, 0)
}
```

こんな感じになります。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3233313932332f63653530633462312d356236652d363462332d653962312d3837313431333330613263332e676966.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/263752a8-2329-9c81-cfca-f97d0989081a.gif)


Goで書いたコードがクライアントサイドで動くのが新鮮✨

## 画像を使ってみる

canvasを使っていっぱいGopher君を出してみます。

`<script>` の部分は👆と同じです。

```html:sample.html
<!doctype html>
<html>
    <body>
...
        <canvas width="1500" height="1000" id="sample"></canvas>
    </body>
</html>

```



```go:sample-2.go
package main

import (
	"math/rand"
	"syscall/js"
	"time"
)

var (
	document = js.Global().Get("document")
	img      = js.Global().Call("eval", "new Image()")
)

func main() {
	img.Set("src", "./image/gopher.png")

	body := document.Call("getElementsByTagName", "body").Index(0)
	body.Call("addEventListener", "mousemove", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		draw()
		return nil
	}))

	<-make(chan struct{}, 0)
}

func draw() {
	canvas := document.Call("getElementById", "sample")
	ctx := canvas.Call("getContext", "2d")

	rand.Seed(time.Now().UnixNano())
	x := rand.Intn(1000)
	rand.Seed(time.Now().UnixNano() + 100000)
	y := rand.Intn(1000)

	ctx.Call("drawImage", img, x, y)
}

```

![c4iqw-frq4s.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/cabc8852-bfdf-9ddb-389a-b0c6642ead88.gif)
<SUB>* The Go gopher（Gopherくん）は、Renée Frenchによってデザインされました。
</SUB>


このくらいの処理だとJSと体感差はなく、もっと重い画像/動画処理や物理演算などでないとパフォーマンスの差は感じられなそうです。
スマホくらいのcpuだと若干ラグがあったりするかも

## まとめ
* [shimmer](https://github.com/agnivade/shimmer)のような画像処理など実装してみたかったが、使えるパッケージに制約があったりで結構しんどそうだったので今回はここまで


* コンパイルは早いし、別途インストールする必要もないので非常に手軽

* サーバー側をGoで書いている場合、サーバー側で定義している値・ロジックなどを使用できて、クライアント側に流出しないのは強みになりそう

* `syscall/js` 叩きすぎるとパフォーマンスがもの凄く下がるので、使い方は慣れが必要そう

* 現状GCがなかったり、デバッグがかなりしづらかったり、必要な場面に遭遇しづらいなどなどありますが、今後Web領域以外での使用や、言語側の改善も含めて追っておいて損はなさそう