【入門編】WebAssemblyでブラウザを動かす!Goコードをフロントエンドに持っていく方法 – 実行環境・ランタイム・コンパイラ生産性向上バイブル

はじめに:Goのパワーをそのままブラウザへ持ち込もう

「サーバーサイドやCLIツールで書き慣れたGoの資産を、そのままブラウザ上で動かせたらいいのに……」

そう思ったことはありませんか?例えば、複雑な暗号化・復号処理、画像や音声のバイナリパース、業務ロジックのバリデーションなどをフロントエンドでも共通化したい場面は数多くあります。従来はJavaScript/TypeScriptで再実装するか、API経由でサーバーに問い合わせる必要がありました。

しかしWebAssembly(Wasm)の登場により、その常識は過去のものになりました。Goで書いたコードをブラウザネイティブの速度で安全に実行できる時代がすでに来ています。

今回は、Goエンジニアなら絶対に知っておきたい「GoコードをWebAssemblyにコンパイルしてブラウザ上で動かす基礎と実践」を、内部の仕組みからファイルサイズの最適化テクニックまで徹底解説します。これをマスターすれば、フロントエンドとバックエンドの境界を軽々と越えられるようになりますよ!

—

1. GoのWebAssemblyサポート状況と動作メカニズム

まずは全体像を掴んでおきましょう。Goはバージョン1.11から実験的にWasmをサポートし、現在では安定して利用できます。

Goがブラウザで動く仕組み

C言語やRustなどの言語はWasmにコンパイルする際、ランタイムを持たずに極限まで薄いバイナリを吐き出すことが得意です。一方、Goにはガベージコレクション(GC)やゴルーチン(軽量スレッド管理)といった強力なランタイムが備わっています。

GoのWasmバイナリには、この「Goランタイムそのもの」が丸ごと含まれます。そして、ブラウザのJavaScriptエンジンとGoランタイムの仲介役を果たすのが、Go公式から提供されている`wasm_exec.js`というJavaScriptグルーコードです。

+————————————————————-+
| ブラウザ (Chrome / Safari / Firefox / Edge) |
| |
| +——————–+ +————————–+ |
| | JavaScript エンジン | <===> | wasm_exec.js (グルーコード)| |
| +——————–+ +————-+————+ |
| ^ | |
| | (syscall/js による相互呼び出し) | |
| v v |
| +——————————————————-+ |
| | WebAssembly インスタンス (Go ランタイム + アプリコード) | |
| +——————————————————-+ |
+————————————————————-+

この仕組みを理解しておくと、「なぜ`wasm_exec.js`が必要なのか」「なぜバイナリサイズが大きめになるのか」が直感的に腑に落ちるはずです。

—

2. 最速で動かす!Go Wasmのセットアップと「Hello, WebAssembly」

それでは、実際に手を動かしてブラウザのコンソールにログを出力させてみましょう。

ディレクトリ構成

今回は以下のようなシンプルな構成で進めます。

go-wasm-demo/
├── main.go # Goのソースコード
├── main.wasm # コンパイル成果物(生成されます)
├── wasm_exec.js # Go公式のグルーコード(Go環境からコピー)
├── index.html # Wasmをロードして動かすHTML
└── server.go # Wasm配信用のローカルWebサーバー

ステップ1:Goコードの作成 (`main.go`)

標準パッケージの `fmt` を使ってコンソールに出力するだけのコードを用意します。

package main

import “fmt”

func main() {
// この出力はブラウザの開発者ツールの「Console」タブに表示されます
fmt.Println(“🚀 Hello from Go WebAssembly!”)
}

ステップ2:Wasm向けにコンパイルする

Goのクロスコンパイル機能は世界一洗練されています。環境変数 `GOOS=js` と `GOARCH=wasm` を指定するだけで完了します。

macOS / Linux の場合
GOOS=js GOARCH=wasm go build -o main.wasm main.go

Windows (PowerShell) の場合
$env:GOOS=”js”; $env:GOARCH=”wasm”; go build -o main.wasm main.go

ステップ3:`wasm_exec.js` を取得する

お使いのGoのインストール先から、グルーコードをカレントディレクトリにコピーします。

GOROOTから自動でコピーするコマンド
cp “$(go env GOROOT)/lib/wasm/wasm_exec.js” .

※ Go 1.23以前の古いバージョンでは misc/wasm/wasm_exec.js にある場合があります
cp “$(go env GOROOT)/misc/wasm/wasm_exec.js” .

ステップ4:HTMLファイルを作成する (`index.html`)

ブラウザ標準の `WebAssembly.instantiateStreaming` APIを使って、Wasmバイナリをロード・実行します。





Go WebAssembly 入門




Go WebAssembly Demo

ブラウザの開発者ツール(Console)を開いて確認してください。


ステップ5:開発用Webサーバーの起動

Wasmバイナリはセキュリティ上の制約(CORSやMIMEタイプの指定)があるため、`file://` 直開きではなくローカルWebサーバー経由で開く必要があります。

サクッとGoでローカルサーバーを立てましょう。

// server.go
package main

import (
“log”
“net/http”
)

func main() {
port := “:8080”
// カレントディレクトリを静的ファイル配信
http.Handle(“/”, http.FileServer(http.Dir(“.”)))
log.Printf(“サーバーを起動しました -> http://localhost%s\n”, port)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatal(err)
}
}

go run server.go

ブラウザで `http://localhost:8080` を開き、DevTools(F12)のコンソールを確認してください。「🚀 Hello from Go WebAssembly!」が表示されていれば大成功です!

—

3. JSとGoの相互運用:`syscall/js` をマスターする

単にログを出すだけでなく、JavaScriptとデータのやり取りをしたり、DOMを直接操作してこそ真価を発揮します。Go標準の `syscall/js` パッケージを使います。

知っておくべき重要概念

  • `js.Global()`: ブラウザの `window` オブジェクトを取得
  • `js.FuncOf()`: Goの関数をJavaScriptの関数としてラップ
  • Goプロセスの終了防止: Goの `main()` 関数が終了するとWasmインスタンスも破棄されます。JS側から関数を呼び出し続けたい場合は、チャネルを使って `main` を意図的に待機状態にする必要があります。

実践コード:Goの関数をJS側に公開し、DOMを書き換える

`main.go` を以下のように書き換えてみましょう。

package main

import (
“fmt”
“syscall/js”
)

// JavaScriptから呼び出されるGoの関数
// 引数: this (呼び出し元のJSコンテキスト), args (渡された引数配列)
func addNumbers(this js.Value, args []js.Value) any {
if len(args) < 2 { return "引数が足りません" } // JSの数値をGoのfloat64として取得 a := args[0].Float() b := args[1].Float() sum := a + b fmt.Printf("[Go内部ログ] 計算実行: %f + %f = %f\n", a, b, sum) // JavaScript側へ戻り値を返す return sum } // DOMを直接操作する関数 func updateDOM(this js.Value, args []js.Value) any { document := js.Global().Get("document") messageElement := document.Call("getElementById", "result-area") // innerHTMLをGoから直接書き換える messageElement.Set("innerHTML", "GoによってDOMが書き換えられました!“)
return nil
}

func main() {
fmt.Println(“Wasmランタイムが初期化されました。”)

// 1. JavaScriptのグローバル領域 (window) にGoの関数を登録
js.Global().Set(“goAdd”, js.FuncOf(addNumbers))
js.Global().Set(“goUpdateDOM”, js.FuncOf(updateDOM))

// 2. Goのプログラムが終了しないように無限待機させる
// これがないと、JSから `goAdd` を呼ぶ前にプログラムが終了してエラーになります
c := make(chan struct{})
<-c }

HTML側のコードを拡張





Go WebAssembly Interop



Go WebAssembly 相互運用デモ

+




再コンパイルしてブラウザをリロードしてみましょう。

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

ボタンをクリックすると、Go内部で計算された結果が即座に反映され、DOM操作もシームレスに動作することが確認できます。

—

4. 現場の最大の壁「バイナリサイズ問題」をどう突破するか?

Go Wasmをプロダクション導入する際、誰もが直面するのが生成される `.wasm` ファイルのサイズ問題です。

標準の `go build` でビルドすると、最小構成でも 2MB〜3MB 程度のサイズになります。一般的なWebフロントエンドのリソースとしては少々大きめです。

実務で使えるサイズまで軽量化する 3つの実践テクニック を紹介します。

テクニック1:リンカーフラグでデバッグ情報を削る

まずは基本ですが、シンボル情報とデバッグ情報を削除(ストリップ)します。

GOOS=js GOARCH=wasm go build -ldflags=”-s -w” -o main.wasm main.go

  • `-s`: シンボルテーブルの削除
  • `-w`: DWARFデバッグ情報の削除

これだけでサイズが 約20〜30% 削減 されます。

—

テクニック2:TinyGo(タイニーゴー)を活用する【効果絶大】

もしWebブラウザ向けに極限まで軽量化したい場合、組み込み&Wasm向けに特化したGoコンパイラ [TinyGo](https://tinygo.org/) を使うのが業界のデファクトスタンダードです。

TinyGoは独自の超軽量ランタイムとLLVMベースの最適化パイプラインを使用するため、ビルドサイズを劇的に落とせます。

TinyGoでビルドする場合
tinygo build -o main.wasm -target wasm main.go

> 注意点:
> TinyGoを使う場合は、`wasm_exec.js` もTinyGo専用のものを使用する必要があります。
>
> cp $(tinygo env TINYGOROOT)/targets/wasm_exec.js .
>

サイズ比較の目安

| コンパイラ / 最適化 | バイナリサイズ |
| :— | :— |
| 標準 `go build` | 約 2.5 MB |
| 標準 `go build -ldflags=”-s -w”` | 約 1.8 MB |
| TinyGo | 約 50 KB 〜 200 KB (劇的!) |

※標準ライブラリの `reflect` や `net/http` を多用する場合はTinyGoで一部制約がありますが、UIロジックや計算処理であればTinyGoが圧倒的なパフォーマンスを発揮します。

—

テクニック3:配信時に Gzip / Brotli 圧縮を効かせる

Wasmバイナリは構造上、圧縮効率が非常に高いです。Webサーバー(NginxやCloudflareなど)側で Brotli (`.br`) や Gzip (`.gz`) 配信を有効化してください。

  • 2.5MBのWasmファイルであっても、Brotli圧縮をかけると 500KB〜600KB 前後まで一気に縮みます。

—

5. アーキテクトの視点:Go Wasmのベストな使い所

GoでWasmが動くからといって、ReactやVueの代わりに画面の構築すべてをGoで書くのはおすすめしません(エコシステムや開発効率の観点から)。

現場で最もレバレッジが効くのは、以下のようなハイブリッド構成です。

1. ビジネスロジック・バリデーションの共通化
サーバー側(Go)で使用しているバリデータやドメインロジックをWasm化し、フロントエンドに事前配布して入力直後に同一ルールで高速検証する。
2. 重いクライアントサイド計算
Canvasの画像ピクセル処理、MarkdownやDSLの独自パーサー、暗号化/署名生成処理。
3. 既存の優秀なGoライブラリの流用
Goエコシステムにしかない優れたアルゴリズム資産をブラウザでそのまま再利用する。

—

まとめ

GoによるWebAssembly開発は、想像以上に手軽で強力です。

1. `GOOS=js GOARCH=wasm go build` で誰でも一瞬でWasmバイナリが作れる
2. `syscall/js` を使えば、JavaScriptの関数呼び出しもDOM操作も自由自在
3. 本格運用の際は `-ldflags=”-s -w”` や TinyGo、Brotli圧縮 を組み合わせてサイズを最小化する

Goの強みである「シンプルさ」「堅牢性」をフロントエンドの世界に持ち込むことで、Webアプリケーションの設計の幅は無限に広がります。まずは手元の小さな計算ロジックから、ブラウザ上でGoを動かしてみませんか?

タイトルとURLをコピーしました