Go言語でWebAssemblyを実装


簡単なWebAssemblyを実装

モジュールディレクトリの作成とgo.modを生成します。

mkdir wasm
cd wasm
go mod init example.com/wasm

main.goを作成します。

package main

import "fmt"

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

Go言語のビルドコマンドに環境変数GOOS = jsGOARCH = wasmを設定して、main.goをWebAssembly用にコンパイルしてmain.wasmを生成します。

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

main.wasmだけでは動かないので、wasm_exec.js(JavaScriptサポートファイル)をコピーします。

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

wasm_exec.jsを使ってmain.wasmを実行する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>

あとは、WebサーバーのDocumentRootにindex.htmlmain.wasmwasm_exec.jsを配置してブラウザでアクセスすれば、コンソール(開発ツール)に「Hello, WebAssembly!」の文字列が表示されます。

DocumentRoot
├── index.html
├── main.wasm
└── wasm_exec.js

Go言語でJavaScriptを制御する

このままだとmain.wasmが実行後すぐに終了してしまい、JavaScriptのイベントを受け取れません。そこでチャンネルを生成して、チャンネルを受信するまでmain関数をブロックするようにします。

func main(){
	done := make(chan int)

	// 処理

	<-done
}

JavaScriptを制御するためのsyscall/jsモジュールをimportします。

import "syscall/js"

syscall/jsのパッケージを使えば、JavaScriptをGo言語から制御することも、JavaScriptからGo言語を実行することも可能です。

JavaScriptからGo言語を実行するためのJavaScriptの関数をGo言語で定義する

var goFunc js.Func
defer goFunc.Release()
goFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	arg1 := args[0]
	log.Printf("arg1=%v", arg1) // ブラウザのコンソールに出力
	return nil
})
js.Global().Set("goFunc", goFunc)

Go言語からJavaScriptのjsFunc関数を実行する

js.Global().Get("jsFunc").Invoke(arg1)

※ wasmを実行しているHtmlにjsFunc関数が定義されている必要があります。

JavaScriptのEventListenerにGo言語で定義したJavaScriptの関数を登録する

var onChangeMyText js.Func
defer onChangeMyText.Release()
onChangeMyText = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	event := args[0]                                   // eventオブジェクト
	myText = event.Get("target").Get("value").String() // event.terget.valueのJSコードと同等
	return nil
})
js.Global().
	Get("document").
	Call("getElementById", "myText").
	Call("addEventListener", "change", onChangeMyText)

beforeunloadmイベントにmainを止めているチャンネルにメッセージを送るメソッドを登録する

var onBeforeunload js.Func
defer onBeforeunload.Release()
onBeforeunload = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
	done <- 1 // チャンネルに値を送信してアプリを終了する
	return nil
})
js.Global().
	Call("addEventListener", "beforeunload", onBeforeunload)