ludwig125のブログ

頑張りすぎずに頑張る父

github pages でWASMを使ったGoのWebツールを動かす【その2】(WebAssembly導入)

ページの構成

以下では、WebAssembly を使った Web ページの作成方法を確認します。 この後で、github pages 上で、Go Wasm のページを公開することが目的です。

https://webassembly.org/

公式の説明

WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.

WebAssembly(略称:Wasm)は、stack-baseの仮想マシン用のバイナリ命令形式です。
Wasmは、プログラミング言語用のポータブルなコンパイルターゲットとして設計されており、クライアントおよびサーバーアプリケーションのWeb上でのdeployを可能にします。

The Wasm stack machine is designed to be encoded in a size- and load-time-efficient binary format. WebAssembly aims to execute at native speed by taking advantage of common hardware capabilities available on a wide range of platforms.

Wasmスタックマシンは、サイズとロード時間の効率的なバイナリ形式でエンコードされるように設計されています。
WebAssemblyは、幅広いプラットフォームで利用可能な一般的なハードウェア機能を活用することで、ネイティブスピードで実行することを目指しています。

補足説明

WebAssembly を使用すると、JavaScript と同じように Rust、C、Go などの言語で Web ツールを作成できます。これにより、既存のライブラリを移植したり、JavaScript で利用できない機能を活用したりできます。

また、WebAssembly はバイナリ形式にコンパイルされるためコードの高速実行が可能になります。 JavaScript より速度を上回ることを目標にしているらしいです。 Go でも、Go1.11 から標準の機能として Go のコードを WebAssembly にコンパイルする機能が追加されました。

今の自分の Go のバージョンは以下の通りでした。

[~/go/src/github.com/ludwig125/githubpages] $go version
go version go1.17 linux/amd64

Go WebAssembly

https://github.com/golang/go/wiki/WebAssembly#getting-started

を参考に進めます。

Getting Started

まずは簡単なプログラムを作成します。

main.go

package main

import "fmt"

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

このコードを WebAssembly 形式で、build するには以下のようにします。

Go にはクロスコンパイルという機能で、別のアーキテクチャや別の OS 向けのバイナリをビルドすることができます。 ここでは、 GOOSjsに、GOARCHwasmにすることで、wasm 用のファイルにしています。

また、-omain.wasmを指定したので、この名前の実行可能な WebAssembly ファイルが作られることになります。

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

この main.wasmをブラウザ上で実行するために、Javascript と HTML が必要になります。

Go の最近のバージョンにはデフォルトで wasm 用の javascript(js)が同封されているので、それを以下のように手元に持ってきます。

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

また、以下の通り、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>

上のコードで重要なのは以下の2つです

  • <script src="wasm_exec.js"></script>
  • WebAssembly.instantiateStreaming
    • これは Javascript API で、wasm ファイルの読み込みを可能にします

https://github.com/golang/go/wiki/WebAssembly#getting-started には、ブラウザがWebAssembly.instantiateStreamingに対応していない場合は polyfillを使うようにと書かれていますが、私の環境では普通に実行できたのでここではこのまま使用しました。

polyfill

この辺の WASM を使う場合の説明は以下が詳しいです

ここまでの段階で以下のファイルが存在します。

[~/go/src/github.com/ludwig125/githubpages] $ls
index.html  main.go  main.wasm*  wasm_exec.js

これを Web サーバ上で実行するために、 goexec を使います。 もちろん、別途 Go でサーバプログラムを作ってもいいです( 例:https://go.dev/play/p/pZ1f5pICVbV )が、ここでは公式ドキュメントに従って以下のように goexec でサーバを立てます。

goexec の install(初回のみ)

$ go get -u github.com/shurcooL/goexec

goexec でサーバ起動(ここでは Port 8080 でサーバを立ち上げています)

$ goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

注意:うまく動かないときは以下の通り Go の環境設定をする必要があります

また、goexec 実行時に以下のようなエラーが出た場合は、すでに同じ Port で goexec を起動していてバッティングしている可能性があります

(*net.OpError)(&net.OpError{
        Op:     (string)("listen"),
        Net:    (string)("tcp"),
        Source: (net.Addr)(nil),
        Addr: (*net.TCPAddr)(&net.TCPAddr{
                IP:   (net.IP)(nil),
                Port: (int)(8080),
                Zone: (string)(""),
        }),
        Err: (*os.SyscallError)(&os.SyscallError{
                Syscall: (string)("bind"),
                Err:     (syscall.Errno)(0x62),
        }),
})

サーバ起動した状態でブラウザでhttp://localhost:8080/にアクセスします。 ちなみに、公式ドキュメントにはhttp://localhost:8080/index.html となっていますが、普通の Web サーバでは http://localhost:8080/のようにスラッシュで終わる URL にアクセスすると自動でindex.htmlを探すようになっているので同じことです。

この Web ページ上で、JavaScriptデバッグコンソールを開きます。 Chrome では、F12 で開けます。

image

go wasm を github pages で動かす

2021/12/30 の時点で、github pages で Web ページを公開する方法は3通りしかないようです

github リポジトリでは今後masterではなくmainブランチがデフォルトになったので、今回はmainブランチのdocs/以下に wasm ファイルをおいてみます。

[~/go/src/github.com/ludwig125/githubpages] $ls docs
index.html  main.go  main.wasm*  wasm_exec.js

これで、以下の通り、mainブランチのdocs/を選んでSaveします。

image

30 秒ほど待つと、 https://ludwig125.github.io/githubpages/に更新が反映されて以下の通り、go wasm の結果が見られるようになりました。

image

これで、githubpages で Go の wasm の Web ページを公開することができるようになりました。

以降、main ブランチを修正すれば、この Web ページも更新されるはずです。 毎回反映を待つのが嫌だったり、ローカルで確認したい場合はgoexecを使えばいいわけです。