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=js`と`GOARCH=wasm`環境変数を設定します

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

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

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

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.html`、`wasm_exec.js`、`main.wasm`)をWebサーバーから提供します。たとえば、`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

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

最後に、http://localhost:8080/index.htmlに移動し、JavaScriptデバッグコンソールを開くと、出力が表示されます。プログラムを変更し、`main.wasm`を再構築し、更新して新しい出力を確認できます。

Node.jsを使用したWebAssemblyの実行

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

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

次に、`$(go env GOROOT)/lib/wasm`を`PATH`に追加します。これにより、`go run`と`go test`は`PATH`検索で`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`は、Go WasmバイナリをNodeで実行できるようにするラッパーです。デフォルトでは、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を使用して、ブラウザ内でテストを実行することもできます。これは、Webサーバーの起動を自動化し、ヘッドレス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を参照してください。

また

キャンバス

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

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

そのため、たとえば、リクエストの作成中にモードを「cors」に設定する場合、次のようなものになります

req, err := http.NewRequest("GET", "http://localhost: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`)があり、読み込み時間を大幅に短縮するはずです。詳細はこちらこちら

その他の例

一般

キャンバス(2D)

データベース

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コードエクスプローラは、WebAssemblyファイルの構造を視覚化するのに役立ちます。

Wasmファイルサイズの削減

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

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

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

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

    @johanbrandhorstからの例

    例1

    サイズ コマンド 圧縮時間
    16M (非圧縮サイズ) 該当なし
    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 (非圧縮サイズ) 該当なし
    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. Wasmファイルの生成にTinyGoを使用します。

    TinyGoは、組み込みデバイスを対象としたGo言語のサブセットをサポートし、WebAssembly出力ターゲットを備えています。

    制限はありますが(まだ完全なGo実装ではありません)、かなり高機能であり、生成されるWasmファイルは…小さいです。約10kBは珍しくありません。「Hello world」の例は575バイトです。 gz -6すると、408バイトに減少します。 :wink

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

その他のWebAssemblyリソース


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