Go Wiki: WebAssembly

はじめに

Go 1.11でWebAssemblyへの実験的なポートが追加されました。Go 1.12ではその一部が改善され、Go 1.13でさらなる改善が期待されています。Go 1.21ではWASI syscall APIを対象とする新しいポートが追加されました。

WebAssemblyは、そのホームページで次のように説明されています。

WebAssembly (略称 Wasm) は、スタックベースの仮想マシンのためのバイナリ命令フォーマットです。Wasmは、C/C++/Rustなどの高水準言語のコンパイルのための移植可能なターゲットとして設計されており、クライアントおよびサーバーアプリケーションのWebへの展開を可能にします。


WebAssemblyに初めて触れる方は、はじめにセクションを読み、Go WebAssemblyの講演のいくつかを見てから、以下のその他の例をご覧ください。


JavaScript (GOOS=js) ポート

はじめに

このページは、Go 1.11以降の機能的なインストールを前提としています。トラブルシューティングについては、インストールトラブルシューティングページを参照してください。

Windowsをご利用の場合は、Git BashなどのBASHエミュレーションシステムを使用してこのチュートリアルを進めることをお勧めします。

Go 1.23以前の場合、この記事で必要なwasmサポートファイルはmisc/wasmにあり、lib/wasm/wasm_exec.jsなどのファイルを使った操作を行う際には、パスを置き換える必要があります。

基本的なGoパッケージをWeb用にコンパイルするには

package main

import "fmt"

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

WebAssembly用にコンパイルするには、GOOS=jsGOARCH=wasmの環境変数を設定します。

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

これにより、パッケージがビルドされ、main.wasmという実行可能なWebAssemblyモジュールファイルが生成されます。.wasmファイル拡張子を使用すると、後で正しいContent-TypeヘッダーでHTTP経由で提供しやすくなります。

メインパッケージのみをコンパイルできることに注意してください。それ以外の場合、WebAssemblyで実行できないオブジェクトファイルが生成されます。WebAssemblyで使用したいパッケージがある場合は、それをメインパッケージに変換してバイナリをビルドしてください。

main.wasmをブラウザで実行するには、JavaScriptサポートファイルと、すべてを結合するためのHTMLページも必要です。

JavaScriptサポートファイルをコピーする

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

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>

ブラウザがまだWebAssembly.instantiateStreamingをサポートしていない場合、ポリフィルを使用できます。

次に、3つのファイル(index.htmlwasm_exec.js、およびmain.wasm)をウェブサーバーから提供します。たとえば、goexecを使用する場合

# install goexec: go install github.com/shurcooL/goexec@latest
goexec 'http.ListenAndServe(`:8080`, http.FileServer(http.Dir(`.`)))'

または、独自の基本的なHTTPサーバーコマンドを使用してください。

注:コンパイラとwasm_exec.jsサポートファイルは、同じ主要なGoバージョンを一緒に使用する必要があります。つまり、main.wasmファイルがGoバージョン1.Nを使用してコンパイルされた場合、対応するwasm_exec.jsファイルもGoバージョン1.Nからコピーする必要があります。他の組み合わせはサポートされていません。

注: Unix系システムでgoexecコマンドを機能させるには、Goのパス環境変数をシェルのprofileに追加する必要があります。Goの入門ガイドでこれについて説明されています。

/usr/local/go/binをPATH環境変数に追加します。これは、/etc/profile (システム全体のインストールの場合) または$HOME/.profile にこの行を追加することで実行できます。

export PATH=$PATH:/usr/local/go/bin

注: プロファイルファイルへの変更は、次回コンピューターにログインするまで適用されない場合があります。

最後に、https://:8080/index.htmlにアクセスし、JavaScriptデバッグコンソールを開くと、出力が表示されるはずです。プログラムを変更し、main.wasmを再構築し、更新すると新しい出力を確認できます。

Node.jsでのWebAssemblyの実行

コンパイルされたWebAssemblyモジュールをブラウザではなくNode.jsを使って実行することができ、これはテストや自動化に役立ちます。

まず、Nodeがインストールされ、PATHに含まれていることを確認してください。

次に、$(go env GOROOT)/lib/wasmPATHに追加します。これにより、go rungo testPATH検索でgo_js_wasm_execを見つけ、js/wasmで正しく機能させることができます。

$ export PATH="$PATH:$(go env GOROOT)/lib/wasm"
$ GOOS=js GOARCH=wasm go run .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test
PASS
ok      example.org/my/pkg  0.800s

Go自体で作業している場合、これによりrun.bashもシームレスに実行できるようになります。

go_js_wasm_execは、NodeでGo Wasmバイナリを実行できるようにするラッパーです。デフォルトでは、Goインストールのlib/wasmディレクトリにあります。

PATHに何も追加したくない場合は、go runまたはgo testを手動で実行する際に、-execフラグをgo_js_wasm_execの場所として設定することもできます。

$ GOOS=js GOARCH=wasm go run -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec" .
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -exec="$(go env GOROOT)/lib/wasm/go_js_wasm_exec"
PASS
ok      example.org/my/pkg  0.800s

最後に、このラッパーはGo Wasmバイナリを直接実行するためにも使用できます。

$ GOOS=js GOARCH=wasm go build -o mybin .
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./mybin
Hello, WebAssembly!
$ GOOS=js GOARCH=wasm go test -c
$ $(go env GOROOT)/lib/wasm/go_js_wasm_exec ./pkg.test
PASS
ok      example.org/my/pkg  0.800s

ブラウザでのテストの実行

wasmbrowsertestを使用すると、ブラウザ内でテストを実行することもできます。これは、ウェブサーバーの起動を自動化し、ヘッドレスChromeを使用してその中でテストを実行し、ログをコンソールに中継します。

以前と同様に、バイナリを取得するにはgo get github.com/agnivade/wasmbrowsertestを実行するだけです。それをgo_js_wasm_execに名前変更し、PATHに配置してください。

$ mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec
$ export PATH="$PATH:$GOPATH/bin"
$ GOOS=js GOARCH=wasm go test
PASS
ok      example.org/my/pkg  0.800s

あるいは、execテストフラグを使用してください。

GOOS=js GOARCH=wasm go test -exec="$GOPATH/bin/wasmbrowsertest"

DOMとのインタラクション

https://pkg.go.dev/syscall/jsをご覧ください。

その他

  • app: カスタムツールを備えたPWA互換のReactベースフレームワーク。

  • dom: DOM操作を効率化するためのライブラリが開発中です。

  • dom: JavaScript DOM APIのGoバインディング。

  • domui: 完全なGUIアプリケーションを作成するための純粋なGoフレームワーク。

  • gas: WebAssemblyアプリケーション用のコンポーネントベースのフレームワーク。

  • GoWebian: 純粋なGoでページを構築し、WebAssemblyバインディングを追加するためのライブラリ。

  • hogusuru: ほとんどのブラウザ機能 (indexeddb, serviceworker, websocketなど) にGOから直接アクセスできるように実装した高度なwebassemblyフレームワーク。

  • VECTY: ReactやVueJSのような最新のWebフレームワークと競合する、WebAssemblyを使用してGoでレスポンシブかつダイナミックなWebフロントエンドを構築します。

  • vert: GoとJSの値間のWebAssembly相互運用。

  • vue: WebAssemblyアプリケーションのためのプログレッシブフレームワーク。

  • Vugu: アプリケーションロジックにGoを使ったHTMLレイアウト、単一ファイルコンポーネント、高速開発・プロトタイピングワークフローを特徴とするwasmウェブUIライブラリ。

  • webapi: DOM、HTML、WebGLなどのバインディングジェネレータと生成されたバインディング。

  • webgen: HTMLでコンポーネントを定義し、webapiを使用してGo型とコンストラクタ関数を生成します。

Canvas

net/httpを使用する際のフェッチオプションの設定

net/httpライブラリを使用してGoからHTTPリクエストを行うことができ、それらはfetch呼び出しに変換されます。ただし、fetchのオプションとhttpクライアントオプションの間に直接のマッピングはありません。これを実現するために、fetchオプションとして認識される特別なヘッダー値があります。それらは次のとおりです。

  • js.fetch:mode: Fetch APIのmode設定へのオプション。有効な値は「cors」、「no-cors」、「same-origin」、「navigate」です。デフォルトは「same-origin」です。

  • js.fetch:credentials: Fetch APIのcredentials設定へのオプション。有効な値は「omit」、「same-origin」、「include」です。デフォルトは「same-origin」です。

  • js.fetch:redirect: Fetch APIのredirect設定へのオプション。有効な値は「follow」、「error」、「manual」です。デフォルトは「follow」です。

したがって、例として、リクエストを行う際にモードを「cors」に設定したい場合は、次のようになります。

req, err := http.NewRequest("GET", "https://:8080", nil)
req.Header.Add("js.fetch:mode", "cors")
if err != nil {
  fmt.Println(err)
  return
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
  fmt.Println(err)
  return
}
defer resp.Body.Close()
// handle the response

より詳しい情報や新しい情報については、#26769にご自由に登録してください。

ChromeにおけるWebAssembly

新しいバージョンのChromeを使用している場合、新しいコンパイラであるLiftoffを有効にするフラグ (chrome://flags/#enable-webassembly-baseline) があり、これにより読み込み時間が大幅に改善されるはずです。詳細情報はこちら

その他の例

一般

Canvas (2D)

  • GoWasm Experiments - いくつかの一般的な呼び出しタイプの動作コードをデモンストレーションします
    • bouncy
    • rainbow-mouse
    • 反発
    • bumpy - 2Dキャンバスと2D物理エンジンを使用しています。画面をクリックしてオブジェクトを作成し、重力が作用するのを見てください!
    • アーティ
    • hexy (新作)
  • Gomeboycolor-wasm
    • 実験的なゲームボーイカラーエミュレータのWASMポート。対応するブログ記事には、興味深い技術的な洞察が含まれています。
  • TinyGoキャンバス
    • これは標準のGoではなくTinyGoでコンパイルされており、結果として19.37kB (圧縮済み) のwasmファイルになります。
  • 車とマウス
    • カーソルで小さなキャンバスに描かれた車を誘導してポイントを獲得するゲーム

データベース

  • TiDB-Wasm - Go言語で書かれたデータベースであるTiDBをWasm上でブラウザで実行します。

WebGLキャンバス (3D)

WASI (GOOS=wasip1) ポート

はじめに (WASI)

Go 1.21では、サポートされるプラットフォームとしてWASIが導入されました。WASI向けにビルドするには、wasip1ポートを使用します。

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

公式ブログには、WASIポートを使用するための役立つ紹介記事があります。https://go.dokyumento.jp/blog/wasi

Go WebAssemblyの講演

エディタの設定

デバッグ

WebAssemblyにはまだデバッガのサポートがないため、現時点ではJavaScriptコンソールに結果を表示するために昔ながらのprintln()アプローチを使用する必要があります。

この問題に対処するため、公式のWebAssemblyデバッグサブグループが作成され、いくつかの初期調査と提案が進行中です。

デバッガ側に興味がある方は、ぜひ参加してこの推進にご協力ください。:smile

WebAssemblyファイルの構造解析

WebAssembly Code Explorerは、WebAssemblyファイルの構造を視覚化するのに役立ちます。

  • 左側の16進数の値をクリックすると、それが属するセクションと、右側の対応するテキスト表現が強調表示されます。
  • 右側の行をクリックすると、左側でその行の16進バイト表現が強調表示されます。

Wasmファイルのサイズ削減

現在、Goは大きなWasmファイルを生成し、最小サイズでも約2MB程度になります。Goコードがライブラリをインポートすると、このファイルサイズは劇的に増加する可能性があります。10MB以上になることも珍しくありません。

このファイルサイズを削減する主な方法は(今のところ)2つあります。

  1. .wasmファイルを手動で圧縮する。

    • gz圧縮を使用すると、約2MB(最小ファイルサイズ)のサンプルWASMファイルが約500kBに削減されます。gzip --bestよりも良い結果が得られるため、gzip圧縮にはZopfliを使用する方が良いかもしれませんが、実行にははるかに時間がかかります。
    • Brotliを圧縮に使用すると、ファイルサイズはZopfliとgzip --bestの両方よりも顕著に優れており、圧縮時間もその中間に位置します。この(新しい) Brotliコンプレッサーは妥当に見えます。

    @johanbrandhorstの例

    例1

    サイズ コマンド 圧縮時間
    16M (非圧縮サイズ) N/A
    2.4M brotli -o test.wasm.br test.wasm 53.6秒
    3.3M go-zopfli test.wasm 3分2.6秒
    3.4M gzip --best test.wasm 2.5秒
    3.4M gzip test.wasm 0.8秒

    例2

    サイズ コマンド 圧縮時間
    2.3M (非圧縮サイズ) N/A
    496K brotli -o main.wasm.br main.wasm 5.7秒
    640K go-zopfli main.wasm 16.2秒
    660K gzip --best main.wasm 0.2秒
    668K gzip main.wasm 0.2秒

    利用可能な場合は、https://github.com/lpar/gzippedのようなものを使用して、圧縮ファイルを正しいヘッダーで自動的に提供してください。

  2. 代わりにTinyGoを使用してWasmファイルを生成します。

    TinyGoは、組み込みデバイスをターゲットとしたGo言語のサブセットをサポートしており、WebAssembly出力ターゲットも持っています。

    制限事項(まだ完全なGo実装ではない)はあるものの、かなり高性能で、生成されるWasmファイルは…非常に小さいです。〜10kBは珍しくありません。「Hello world」の例は575バイトです。これをgz -6で圧縮すると、408バイトまで小さくなります。:wink

    このプロジェクトも非常に活発に開発されており、その機能は急速に拡大しています。TinyGoでWebAssemblyを使用する方法の詳細については、https://tinygo.org/docs/guides/webassembly/をご覧ください。

その他のWebAssemblyリソース

  • Awesome-Wasm - さらなるWasmリソースの広範なリスト。Goに特化したものではありません。

このコンテンツはGo Wikiの一部です。