Help us understand the problem. What is going on with this article?

Go x WebAssembly ってどんな?

この記事は 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形式にビルドします。
さすがビルド速いです

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サーバーを立てておきます。

server/server.go
package main

import "net/http"

func main() {
    http.ListenAndServe(":8080", http.FileServer(http.Dir("../")))
}
$ go run server/server.go

4. ブラウザで確認

Run ボタンをクリックすると以下のようにコンソールに出力されます

スクリーンショット 2019-12-09 0.36.30.png

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

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

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

JSで簡単な操作をしてみる

👆のファイルを少々手直しして、JSっぽいことをしてみます。

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

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叩いてます

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

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

画像を使ってみる

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

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

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

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
* The Go gopher(Gopherくん)は、Renée Frenchによってデザインされました。

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

まとめ

  • shimmerのような画像処理など実装してみたかったが、使えるパッケージに制約があったりで結構しんどそうだったので今回はここまで

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

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

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした