2021年10月26日火曜日

golang で WebAssembly 超入門

golang で WebAssembly 超入門

概要

WebAssembly は主にブラウザで動作する言語でバイトコードを使うため JavaScript よりも高速に動作します
また golang や Rust などサーバサイドのコードから .wasm と呼ばれるコンパイルされた WebAssembly コードを吐き出すことができこれがブラウザで動作します
つまり golang や Rust のコードがブラウザで動かせるような技術になります

今回は HelloWorld をやってみました

環境

  • macOS 11.6
  • golang 1.17
  • Chrome 94.0.4606.71

golang のコード

まずは普通に golang のコードを書いてみます
とりあえず「Hello」と出力するだけのコードを記載します

  • vim main.go
package main

import "fmt"

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

.wasm ファイルを作成する

go build する際にいくつかのオプションを指定するだけで OK です

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

これで main.wasm というファイルが作成されています

% ls -l main.wasm 
-rwxr-xr-x  1 hawk  staff  2020036 10 14 16:22 main.was

index.html の作成

作成した .wasm ファイルをブラウザ上で動かします
main.wasm と index.html は同じディレクトリ上に配置してください

  • vim 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>

wasm_exec.js の配置

golang を brew でインストールしているのであれば以下のようなパスにあるはずです
コピーして同じディレクトリに配置します

  • cp /usr/local/Cellar/go/1.17/libexec/misc/wasm/wasm_exec.js .

動作確認

サーバを立てましょう
なんでも OK です
今回は Python を使います

  • python3.9 -m http.server 8000

これでブラウザで localhost:8000 にアクセスしブラウザのコンソール情報を見ると以下のようにメッセージが表示されているのが確認できると思います

DOM を操作する

syscall/js を使うと golang から dom を操作できます
DOM は Get/Set で取得、設定ができます
DOM のメソッドをコールしたい場合は Call() を使います

DOM から取得できる値はすべて js パッケージの型として取得できるので golang で扱う場合には型の変換が必要です

main では WebAssembly 上で golang が終了しないようにチャネルを作成しています

HTML 側で作成したボタンがクリックされたときにコールされるメソッドを golang 側に実装します
今回は countup というメソッドが呼ばれるようにしかつその結果は id=counter の div タグに設定するようにしています

  • vim main.go
package main

import (
	"strconv"
	"syscall/js"
)

func countup(this js.Value, i []js.Value) interface{} {
	c := js.Global().Get("document").Call("getElementById", "counter").Get("textContent")
	current, _ := strconv.Atoi(c.String())
	js.Global().Get("document").Call("getElementById", "counter").Set("textContent", current+1)
	return 0
}

func registerCallbacks() {
	js.Global().Set("countup", js.FuncOf(countup))
}

func main() {
	c := make(chan struct{}, 0)
	registerCallbacks()
	<-c
}

HTML 側も少しだけ修正します
ボタンと結果を表示する div を追加するだけです

  • vim 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>
    <div id="counter">0</div>
    <button onClick="countup()">up</button>
  </body>
</html>

これで再度ビルドして python で動作確認するとちゃんとカウントアップボタンが動作することが確認できると思います

  • GOOS=js GOARCH=wasm go build -o main.wasm
  • python3.9 -m http.server 8000

最後に

golang で WebAssembly に入門してみました
現在だと開発もかなり進んでおりドキュメントも豊富なので簡単に開発できると思います

WebAssembly を直接書くのは面倒なので golang に慣れている人はこの方法を使うと簡単に WebAssembly を使うことができると思います

WebAssembly で有名な Rust も同じような感じだと思います

参考サイト

0 件のコメント:

コメントを投稿