Go WebAssemblyでブラウザ上でGoを実行する その2 Go側からJavascriptを操作する
前回の記事「GoのコードをWebAssenblyにコンパイルしてブラウザ上でGoを実行する」では、コンパイル済みのGoのプログラムをブラウザ上で実行しただけでした。
今回は、さらにGoのプログラム側からJavascriptを操作してみようと思います。
syscall/js
Goのプログラム側からJsにアクセスするには syscall/js ライブラリを利用します。
日本語訳: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
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー