グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



Go WebAssemblyでブラウザ上でGoを実行する その2 Go側からJavascriptを操作する

前回の記事「GoのコードをWebAssenblyにコンパイルしてブラウザ上でGoを実行する」では、コンパイル済みのGoのプログラムをブラウザ上で実行しただけでした。
今回は、さらにGoのプログラム側からJavascriptを操作してみようと思います。

syscall/js

Goのプログラム側からJsにアクセスするには syscall/js ライブラリを利用します。

Package js gives access to the WebAssembly host environment when using the js/wasm architecture.

日本語訳:jsパッケージは、js/wasmアーキテクチャを使用する際に、WebAssemblyホスト環境へのアクセスを提供します。syscall/js

syscall/jsにはブラウザ側のグローバルオブジェクトを取得する機能があり、このグローバルオブジェクトを介してGoとJavascriptでやり取りが可能になります。

Go側からJavascriptのDOMを作成して表示してみる

今回のファイル構成は以下のようになっています。

.
├── docs
│   ├── build.wasm
│   ├── index.html
│   └── wasm_exec.js
├── main.go
└── src
    ├── go.mod
    ├── go.sum
    └── webassembly.go

main.go

index.htmlを表示するためのwebサーバです。

package main

import (
	"log"
	"net/http"
)

func main() {
	port := "8080"
	http.Handle("/", http.FileServer(http.Dir("./docs/")))
	log.Printf("Listen on port: %s", port)
	http.ListenAndServe(":"+port, nil)
}

docs/index.html

wasm_exec.jsを読み込み、src/webassembly.goをビルドしたbuild.wasmを実行しています。

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

src/webassembly.go

 syscall/js をimportしてDOM要素を作成し、html側のbodyに追加します。

package main

import (
	"syscall/js"
)

func main() {
	// グローバルオブジェクト(window)を取得します
	window := js.Global()

	// document オブジェクトを取得します
	document := window.Get("document")

	// bodyを取得します
	body := document.Get("body")

	// h1 のDOMを作成します
	h1 := document.Call("createElement", "h1")
	h1.Set("innerHTML", "Hello Webassembly!")

	// h1をbodyに追加します
	body.Call("appendChild", h1)

	// プログラムが終了しないように待機します
	select {}

}

ビルドコマンド

$ GOOS=js GOARCH=wasm go build -o ../docs/build.wasm

ここまでの状態でmain.goを起動すると以下のようにwebで表示されます。

$ go run main.go

Go側で作成したDOMが追加されていることが確認できました。
ただ、これでは動きがないので、次はGo側でDOMにイベントを設定してみます。

カウンターを動かす

以下のファイルだけ内容を変更して、カウンターを動かしてみます。

src/webassembly.go

package main

import (
	"fmt"
	"strconv"
	"syscall/js"
)

func main() {
	// グローバルオブジェクト(window)を取得します
	window := js.Global()

	// document オブジェクトを取得します
	document := window.Get("document")

	// bodyを取得します
	body := document.Get("body")

	// p のDOMを作成します
	counter := 0
	p := document.Call("createElement", "p")
	p.Set("id", "counter")
	p.Set("innerHTML", strconv.Itoa(counter))

	// ボタンのDOMを作成し、Clickイベントを設定します
	btn := document.Call("createElement", "button")
	btn.Set("textContent", "count up!")
	btn.Call("addEventListener", "click", js.FuncOf(func(js.Value, []js.Value) interface{} {
		counter++
		fmt.Println(counter) // console.logに出力します
		document.Call("getElementById", "counter").Set("innerHTML", strconv.Itoa(counter)) // カウンターの表示を更新します
		return nil
	}))

	// pをbodyに追加します
	body.Call("appendChild", p)
	// ボタンをbodyに追加します
	body.Call("appendChild", btn)

	// プログラムが終了しないように待機します
	select {}

}

ビルドしてサーバを起動して動作を試します。

カウンターが動きました!

さいごに

Go側からグローバルオブジェクトを介してJavascriptの操作を行えました。
普段JavascriptでDOMを操作する感覚のまま扱えますが、Goの型とJavascriptの型の 変換対応を意識しないといけないのでちょっと注意が必要かなと思いました。
以下に対応表を記載しておきます。

Goの型 Javascriptの型
js.Value [its value]
js.Func function
nil null
bool boolean
integers and float number
string string
[]interface{} new array
map[string]interface{} new object

今回のような処理ではGoで書く良さが特にないので次回はjsからGoの関数を使ってみようと思います。

この記事を書いた人

アーティス
アーティス
創造性を最大限に発揮するとともに、インターネットに代表されるITを活用し、みんなの生活が便利で、豊かで、楽しいものになるようなサービスやコンテンツを考え、創り出し提供しています。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします