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を作成して表示してみる
今回のファイル構成は以下のようになっています。
1 2 3 4 5 6 7 8 9 10 |
. ├── docs │ ├── build.wasm │ ├── index.html │ └── wasm_exec.js ├── main.go └── src ├── go.mod ├── go.sum └── webassembly.go |
main.go
index.htmlを表示するためのwebサーバです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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を実行しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<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に追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 {} } |
ビルドコマンド
1 |
$ GOOS=js GOARCH=wasm go build -o ../docs/build.wasm |
ここまでの状態でmain.goを起動すると以下のようにwebで表示されます。
1 |
$ go run main.go |
Go側で作成したDOMが追加されていることが確認できました。
ただ、これでは動きがないので、次はGo側でDOMにイベントを設定してみます。
カウンターを動かす
以下のファイルだけ内容を変更して、カウンターを動かしてみます。
src/webassembly.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
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の関数を使ってみようと思います。

竹内 和也

最新記事 by 竹内 和也 (全て見る)
- Go WebAssemblyでブラウザ上でGoを実行する その2 Go側からJavascriptを操作する - 2022年6月21日
- GoのコードをWebAssenblyにコンパイルしてブラウザ上でGoを実行する - 2022年4月25日
- 【Go】go.modのreplaceでローカルのモジュールを参照する - 2022年2月22日
- goでHTTPリクエストボディのサイズを制限する「MaxBytesReader」 - 2021年12月15日
- 【Go言語】jsonデータをstreamで扱うEncoder、Decoder型を試してみる - 2021年10月17日
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー