この記事は Wano Group Advent Calendar 2019 の12日目の記事となります。
Advent Calendar もそろそろ折り返しですね🎅🎁
GoとWebAssembly
WebAssemblyとは何ぞって話は詳しくはしませんが、ブラウザが読めるアセンブリのコード形式のことです。(詳しくはここ)
特徴としてjavascriptに比べ高速という点が挙げられ、jsの補完的な立ち位置で、モダンブラウザは概ねサポートしています。
Goではv1.11よりWebAssemblyがサポートされるようになり、進行形で様々な改善がされているみたいです。
Officialに色々書いてあります。
ということで勉強がてらGo x WebAssemblyをかなり簡単にですが触ってみて、所感を書こうと思います。
Hello Worldしてみる
公式のGetting Started を参考にやってみます。
1. WebAssemblyバイナリ
スクリプトは特に何も意識することないですね。 これをWebAssembly形式にビルドします。
さすがビルド速いです
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サーバーを立てておきます。
package main
import "net/http"
func main() {
http.ListenAndServe(":8080", http.FileServer(http.Dir("../")))
}
$ go run server/server.go
4. ブラウザで確認
Run
ボタンをクリックすると以下のようにコンソールに出力されます
ディレクトリ構成は以下になります。
|--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っぽいことをしてみます。
ボタンクリック時に背景色を変更させます。
<!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叩いてます
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)
}
こんな感じになります。
Goで書いたコードがクライアントサイドで動くのが新鮮✨
画像を使ってみる
canvasを使っていっぱいGopher君を出してみます。
<script>
の部分は👆と同じです。
<!doctype html>
<html>
<body>
...
<canvas width="1500" height="1000" id="sample"></canvas>
</body>
</html>
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)
}
* The Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。
このくらいの処理だとJSと体感差はなく、もっと重い画像/動画処理や物理演算などでないとパフォーマンスの差は感じられなそうです。
スマホくらいのcpuだと若干ラグがあったりするかも
まとめ
-
shimmerのような画像処理など実装してみたかったが、使えるパッケージに制約があったりで結構しんどそうだったので今回はここまで
-
コンパイルは早いし、別途インストールする必要もないので非常に手軽
-
サーバー側をGoで書いている場合、サーバー側で定義している値・ロジックなどを使用できて、クライアント側に流出しないのは強みになりそう
-
syscall/js
叩きすぎるとパフォーマンスがもの凄く下がるので、使い方は慣れが必要そう -
現状GCがなかったり、デバッグがかなりしづらかったり、必要な場面に遭遇しづらいなどなどありますが、今後Web領域以外での使用や、言語側の改善も含めて追っておいて損はなさそう