🐙

[Go] WebAssembly について調べる

2024/12/09に公開

はじめに

ハッカソンで使う予定ができたので、調べます。

前提

Go 1.11 以降のバージョンに限ります。

wasm ビルドしてみる

main.go を作成し、適当にログを出すコードを書きます。

package main

import "fmt"

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

これをビルドします。
wasm として .go ファイルをビルドするには、コンパイル時に GOOS および GOARCH 変数を設定します。

GOOS=js GOARCH=wasm go build -o main.wasm

実行すると、-o オプションで指定した名前のファイル(今回は main.wasm)が生成されます。

実行してみる

実際に Web サイト上で実行してみます。
wasm を実行するには、Go の lib/wasm 内にある wasm_exec.js が必要です。これをプロジェクトファイルに持ってきます。

cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" .

存在しない場合は、公式リポジトリから直接持ってきてください。
https://github.com/golang/go/blob/master/lib/wasm/wasm_exec.js

実行する土台となる index.html を作成し、以下のように記述します。

<html>
    <head>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script>
            const go = new Go();
            WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
                go.run(result.instance);
            });
        </script>
    </head>
    <body></body>
</html>

index.html をお好きなブラウザーで開くと、開発者ツールのコンソールにログが吐き出されているのがわかります。

ちらと見ましたが、主要なブラウザの最新バージョンでは問題なく対応していそう。

https://caniuse.com/wasm

DOM にアクセスする

できることを調べていきます。
どうやら syscall/js ライブラリを介して DOM からアクセスするっぽい。
https://pkg.go.dev/syscall/js

ドキュメントを取得する

index.html のドキュメント情報を Go 内で扱ってみます。
先に取得するタイトルと h1 タグを追記して、

<html>
    <head>
+       <title>WASM Sample</title>
        <meta charset="utf-8"/>
        <script src="wasm_exec.js"></script>
        <script>
            const go = new Go();
            WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
                go.run(result.instance);
            });
        </script>
    </head>
    <body>
+      <h1>Hello WASM!</h1>
    </body>
</html>

それぞれ取得してみます。

//go:build js && wasm

package main

import (
	"fmt"
	"syscall/js"
)

func main() {
    window := js.Global()
    document := window.Get("document")
    fmt.Println("title: " + document.Get("title").String())
    fmt.Println("h1: " + document.Call(("querySelector"), "h1").Get("innerText").String())
}

実行。問題なさそう。(wasm の再ビルドを忘れずに!)

js/dom を使用して取得する

公式ドキュメントを見てたら、より便利そうな DOM 操作ライブラリがありました。
https://pkg.go.dev/honnef.co/go/js/dom#section-readme

早速使ってみます。

go get honnef.co/go/js/dom/v2
//go:build js && wasm

package main

import (
	"fmt"
-	"syscall/js"
+   "honnef.co/go/js/dom/v2"
)

func main() {
+    window := dom.GetWindow()
+    document := window.Document()
+    fmt.Println("title: " + document.GetElementsByTagName("title")[0].TextContent())
+    fmt.Println("h1: " + document.QuerySelector("h1").TextContent())

-    window := js.Global()
-    document := window.Get("document")
-    fmt.Println("title: " + document.Get("title").String())
-    fmt.Println("h1: " + document.Call(("querySelector"), "h1").Get("innerText").String())
}

例がアレなのでわかりづらいですが、QuerySelector などに対応しています。先程の例では document.Call からすべての関数を呼ぶ必要があったので、こちらのほうが保守性が高そうでいい感じ。

参考

https://go.dev/wiki/WebAssembly

Progate Path コミュニティ

Discussion