Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【登壇資料】GoとWasmでつくる 軽量ブラウザUI

Avatar for エブリー エブリー
February 21, 2026
1

【登壇資料】GoとWasmでつくる 軽量ブラウザUI

20260221 Go Conference mini in Sendai 2026

Avatar for エブリー

エブリー

February 21, 2026
Tweet

More Decks by エブリー

Transcript

  1. 自己紹介 - 名前 - kyo(きょー) - 出身 - 宮城県 栗原市

    - Twitter - https://x.com/Keyl0ve_ - 趣味 - 配信見る、ゲーム、お酒、バイク - 経歴 - 会津大学 → 株式会社エブリー(24新卒)
  2. WebAssembly (Wasm) とは - ブラウザで動作するバイナリ形式の実行コード - W3C 標準仕様 - W3C:

    Web技術の標準化を行う非営利団体 - HTML、CSS、DOM、HTTP、URI/URL、XML などの技術標準(W3C勧告)を策定している - .wasm という拡張子のバイナリファイル - 「JavaScript(JS)と並んで、ブラウザで動く第2の言語」と呼ばれている https://webassembly.org/
  3. このセッションについて セッションで話すこと 1. Go と Wasm の基本構成 2. JS との連携

    3. Go は Wasm でどう動くか 4. 実装の落とし穴 5. デバッグとログ取得のコツ 6. ユースケースと使い分け 7. デモ 8. まとめ セッションのゴール - GoとWasmを用いてJSとの連携を意識した開 発ができるイメージを持ってもらう
  4. Go と Wasm の基本構成 - 必要なファイル一覧 $(go env GOROOT)/lib/wasm/wasm_exec.js ↑

    にあるのでそれをコピーしてくる 開発しやすくするためにローカルにサーバー を立ててファイルを配信している。 本番であれば s3 + cloudfront で ok
  5. Go と Wasm の基本構成 - Wasm にビルド - 環境変数 -

    GOOS=js     ... ターゲットOS(JS環境) - GOARCH=wasm ... ターゲットアーキテクチャ( Wasm) - 結果 - main.wasm が生成される(Goランタイム含めて最小で 2MB程度) - 補足 - TinyGo を使うとサイズ削減可能
  6. Go と Wasm の基本構成 - index.html で Wasm を読み込む 1.

    wasm_exec.js を読み込み(Go ランタイム) 2. new Go() でランタイム初期化 3. fetch("main.wasm") で WASM をダウンロード 4. WebAssembly.instantiateStreaming() でインスタンス化 5. go.run(result.instance) で実行開始
  7. Go と Wasm の基本構成 - 最小例の動作確認 go run server.go Go

    では fmt.Println() があるけど 実行環境はブラウザのはず どうやってコンソール上に表示している?
  8. Go と Wasm の基本構成 - 最小例の動作確認 go run server.go wasm_exec.js

    が fmt.Println() と console.log() を繋げている
  9. Go と Wasm の基本構成 - 最小例の動作確認 go run server.go wasm_exec.js

    が fmt.Println() と console.log() を繋げている wasm_exec.js が繋げられることにも 限界がある そこで
  10. JS との連携 Wasm と JS の役割分担 - Wasm の得意なこと -

    重い計算処理 - 数値計算、画像処理、暗号化 ... - 既存のコードの再利用 - パフォーマンスが重要な処理 - JS の得意なこと - DOM 操作・UI 更新 - イベント処理 - ブラウザ API へのアクセス 連携の基本パターン
  11. JS との連携 Wasm と JS の役割分担 - Wasm の得意なこと -

    重い計算処理 - 数値計算、画像処理、暗号化 ... - 既存のコードの再利用 - パフォーマンスが重要な処理 - JS の得意なこと - DOM 操作・UI 更新 - イベント処理 - ブラウザ API へのアクセス 連携の基本パターン それぞれ得意・不得意があるので 両者の得意分野を活かして協調さ せることが大切
  12. JS との連携 syscall/js で JS と連携 - syscall/js: Go の標準パッケージ

    - 基本的な使い方 - js.Global() ... window オブジェクト - js.Global().Get("関数名") ... JS 関数の取得 - Invoke(引数...) ... JS 関数を呼び出す - Call("メソッド名", 引数...) ... オブジェクトのメソッド を呼び出す - js.ValueOf(値) ... Go 値を JS 値に変換
  13. JS との連携 syscall/js で JS と連携 - Go 関数を JS

    から呼び出し可能に(js.Global().Set)
  14. JS との連携 syscall/js で JS と連携 - Go 関数を JS

    から呼び出し可能に(js.Global().Set) Go は計算、JS は UI 更新 js.Global().Set() で1つだけ公開し、onclick から呼ぶ
  15. JS との連携 syscall/js で JS と連携 - Go 関数を JS

    から呼び出し可能に(js.Global().Set) Go は計算、JS は UI 更新 js.Global().Set() で1つだけ公開し、onclick から呼ぶ js.FuncOf() 受け取る引数は fn func(this Value, args []Value) any) 制約上引数に渡す関数はこの型になっている必要がある js.Global().Set() グローバル関数として公開 (window.add = ... と同等)
  16. JS との連携 syscall/js で JS と連携 - goroutine で非同期処理 →

    JS コールバック 問題なく実行できる
  17. JS との連携 syscall/js で JS と連携 - goroutine で非同期処理 →

    JS コールバック 問題なく実行できる JS との連携周りは ok! じゃあ、ブラウザ環境で Goは今まで通りかける、、?
  18. Go は Wasm でどう動くか GCの挙動 - 挙動 - ビルドされたファイルに GoのGCも含まれている

    → WasmでもGoのGCは既存通り動く - ただし動作するブラウザはシングルスレッド( GOMAXPROCS=1) - サーバーで動くGCはアプリケーションが動く goroutineとは別のgoroutineで動作する - Wasmで動くGCはスレッドが一つしかないため GCが動く時はアプリの実行が止まる - 余談 - WasmGCなるものがあるらしい - GCを持つプログラミング言語を、効率的かつ軽量に Wasmへ移植・実行可能にする機能 - ただGoのGCと設計が違く、実装の見込みはまだなさそう、、、 - https://github.com/golang/go/issues/63904
  19. Go は Wasm でどう動くか goroutine の挙動 - 挙動 - GOMAXPROCS=1

    固定 → 並行はできるが並列(マルチコア)にはならない - channel・select も問題なく使える - サーバーと違い、協調的スケジューリング - 自分が譲るまで他 goroutine は動けない - 注意 - time.Sleep はメインスレッドをブロック → UI が止まる - goroutine 内の panic は recover しないと Wasm 全体が止まる
  20. Go は Wasm でどう動くか ライブラリの互換性 - 動かない → syscall依存 -

    ブラウザ上で動くため、 OSに依存する機能は使えない - os.Open → error: open hoge.txt: not implemented on js - exec.Command → error: exec: "ls": executable file not found in $PATH - 動かない → CGO依存 - WASM 向けには CGO がサポートされていない Cから直接Wasmにすれば良い - ビルド時にエラーが出る → cgo: unknown ptrSize for $GOARCH "wasm" - 動く → Pure Go で完結・syscall を使わない
  21. Go は Wasm でどう動くか ライブラリの互換性 - 動かない → syscall依存 -

    ブラウザ上で動くため、 OSに依存する機能は使えない - os.Open → error: open hoge.txt: not implemented on js - exec.Command → error: exec: "ls": executable file not found in $PATH - 動かない → CGO依存 - WASM 向けには CGO がサポートされていない Cから直接Wasmにすれば良い - ビルド時にエラーが出る → cgo: unknown ptrSize for $GOARCH "wasm" - 動く → Pure Go で完結・syscall を使わない これらの挙動を知っていても実装時に ハマりがちな落とし穴がある
  22. 実装の落とし穴 js.Value の参照保持問題 - 問題 - js.Value は JS のオブジェクトへの参照を保持する

    - Go の GC と JS の GC は別 → 長期保持すると JS 側が解放されにくい - js.FuncOf で作ったコールバックは、使い終わったら Release() 必須 - 悪い例 - js.FuncOf でコールバックを作り addEventListener で登録したまま - Release() を呼ばずに放置する → メモリリーク - 良い例 - イベント登録後、不要になったタイミングで cb.Release() - または onclick + js.Global().Set() で Go 側に関数を 1つだけ露出し、JS 側に Func を増やさない
  23. 実装の落とし穴 js.Value の参照保持問題 - 問題 - js.Value は JS のオブジェクトへの参照を保持する

    - Go の GC と JS の GC は別 → 長期保持すると JS 側が解放されにくい - js.FuncOf で作ったコールバックは、使い終わったら Release() 必須 - 悪い例 - js.FuncOf でコールバックを作り addEventListener で登録したまま - Release() を呼ばずに放置する → メモリリーク - 良い例 - イベント登録後、不要になったタイミングで cb.Release() - または onclick + js.Global().Set() で Go 側に関数を 1つだけ露出し、JS 側に Func を増やさない 以下のようなことが起こる - 動作が重くなる - タブがクラッシュする - 他のタブにも影響
  24. 実装の落とし穴 JS 境界越えのコスト - 前提 - Wasm ↔ JS の呼び出しはコストがかかる

    - 問題があるケース - 毎フレームやループ内で js.Value.Call() を連打すると遅い - データを渡すときも、大きなオブジェクトを何度も渡すとオーバーヘッド - 対策 - バッチ処理としてまとめて一度に処理する - 計算は Wasm 側で完結させ、結果だけ JS に返す - 頻繁に変わる UI は JS 側で更新し、Wasm は「計算結果」だけ返す設計にする
  25. 実装の落とし穴 main の終了 - Go Wasmの挙動 - main が return

    するとプログラム終了 - 対策 - イベント駆動で動かすため、 main で select {} などでブロックする - 悪い例 - 良い例
  26. 実装の落とし穴 panic 時の挙動 - 挙動 - panic が recover されないと、Wasm

    の実行が止まる - ブラウザのコンソールにスタックトレースが出る - ページをリロードするまで Wasm は復帰しない - 対策 - 重要なエントリポイントで defer + recover - 可能な限りエラーを返す設計にし、 panic に頼りすぎない
  27. 実装の落とし穴 panic 時の挙動 - 挙動 - panic が recover されないと、Wasm

    の実行が止まる - ブラウザのコンソールにスタックトレースが出る - ページをリロードするまで Wasm は復帰しない - 対策 - 重要なエントリポイントで defer + recover - 可能な限りエラーを返す設計にし、 panic に頼りすぎない Go・WASM・ブラウザの3層が絡むため、エラー がどの層が原因で起きたか分かりにくい ログと DevTools を使いこなして、原因の層を素 早く絞り込んでいきたい
  28. デバッグとログ取得のコツ デバッグの基本戦略 1. ロジックは Go の単体テストで検証する   → Wasm 化する前に通常の go

    test で通す 2. Wasm 化は「最後のステップ」   → ブラウザ特有の部分(syscall/js)だけを Wasm 用に分離 3. ブラウザでは「ログ」と「DevTools」が命   → fmt.Println と console を活用。スタックトレースを読む
  29. デバッグとログ取得のコツ console.log の活用 - Go 側 - fmt.Println("debug:", value) -

    → ブラウザの DevTools コンソールにそのまま出る - 複数引数・構造化 - fmt.Printf("key=%v\n", data) - js.Global().Get("console").Call("log", "label", js.ValueOf(str)) - ポイント - 重要な分岐やエラー箇所にログを仕込む - 本番用とデバッグ用でビルドタグを分けてもよい
  30. デバッグとログ取得のコツ スタックトレースの読み方 - panic が起きるとコンソールに Go のスタックトレースが出る - ファイル名と行番号は出るが、Wasm 内のアドレスも混ざることがある

    - ソースマップ対応は Go 公式 Wasm では限定的。行番号を頼りにコードを追う 読み方 - 下から上に「どこで呼ばれたか」 を追う(今までのGoと同じ) - 自分のコードの行番号を重点的に
  31. デバッグとログ取得のコツ ブラウザ DevTools の活用 - Network タブ - main.wasm が

    200 でロードされているか - サイズと読み込み時間 - Console タブ - fmt.Println の出力 - panic や JS のエラー - undefined になってるものはないか - Performance タブ - 重い処理がどこで発生しているか Wasm と JS の境界も見える
  32. デバッグとログ取得のコツ ブラウザ DevTools の活用 - Network タブ - main.wasm が

    200 でロードされているか - サイズと読み込み時間 - Console タブ - fmt.Println の出力 - panic や JS のエラー - undefined になってるものはないか - Performance タブ - 重い処理がどこで発生しているか Wasm と JS の境界も見える
  33. デバッグとログ取得のコツ ブラウザ DevTools の活用 - Network タブ - main.wasm が

    200 でロードされているか - サイズと読み込み時間 - Console タブ - fmt.Println の出力 - panic や JS のエラー - undefined になってるものはないか - Performance タブ - 重い処理がどこで発生しているか Wasm と JS の境界も見える 新しいツールは不要 いつもの DevTools をそのまま使える
  34. デバッグとログ取得のコツ デバッグしやすいコードの書き方 - エントリポイントで defer recover し、ログに出す - 複雑な処理は小さな関数に分け、それぞれログ or

    テスト可能に - ログポイントを「境界」(JS に渡す前・受け取った直後)に置くと 原因の切り分けが楽 まとめ - 単体テスト + ログ + DevTools で、Wasm でも十分デバッグできる
  35. デバッグとログ取得のコツ デバッグしやすいコードの書き方 - エントリポイントで defer recover し、ログに出す - 複雑な処理は小さな関数に分け、それぞれログ or

    テスト可能に - ログポイントを「境界」(JS に渡す前・受け取った直後)に置くと 原因の切り分けが楽 まとめ - 単体テスト + ログ + DevTools で、Wasm でも十分デバッグできる 色々Wasmに対して理解は深められた 実際にWasmを使っている実例はある、、?
  36. ユースケースと使い分け Wasm が向いている場面 - 向いている用途 - 高速な計算処理(画像・動画・暗号化・データ処理) - 既存ライブラリの再利用( C/C++/Rust/Go

    資産をブラウザで) - ゲーム・3D、プラグイン・サンドボックス実行 - 実例 - Figma: ファイルの読み込みから描画までの時間を 1/3に短縮 - https://madewithwebassembly.com/showcase/figma - Unity: Unity WebGL のパフォーマンスが向上し、読み込みが高速化 - https://madewithwebassembly.com/showcase/unity/ - 1Password: ページの読み込みと分析が以前の 2 倍以上高速化 - https://madewithwebassembly.com/showcase/1password/ - AutoCAD: 計算負荷の高いデスクトップアプリケーションを Web上で実行できるように - https://madewithwebassembly.com/showcase/autocad/
  37. ユースケースと使い分け Wasm が向いている場面 - 向いている用途 - 高速な計算処理(画像・動画・暗号化・データ処理) - 既存ライブラリの再利用( C/C++/Rust/Go

    資産をブラウザで) - ゲーム・3D、プラグイン・サンドボックス実行 - 実例 - Figma: ファイルの読み込みから描画までの時間を 1/3に短縮 - https://madewithwebassembly.com/showcase/figma - Unity: Unity WebGL のパフォーマンスが向上し、読み込みが高速化 - https://madewithwebassembly.com/showcase/unity/ - 1Password: ページの読み込みと分析が以前の 2 倍以上高速化 - https://madewithwebassembly.com/showcase/1password/ - AutoCAD: 計算負荷の高いデスクトップアプリケーションを Web上で実行できるように - https://madewithwebassembly.com/showcase/autocad/ 「JS では重い・難しい処理をブラウ ザでやりたい」とき
  38. ユースケースと使い分け Wasm・JS 比較表 Wasm JS 実行速度 ◎ 速い △ 用途による

    DOM 操作 △ JS 経由 ◎ 得意 デバッグ △ 工夫が必要 ◎ DevTools 充実 言語選択肢 ◎ 多言語 △ JS/TS のみ ライブラリ △ Pure Go 中心 ◎ エコシステム豊富
  39. ユースケースと使い分け Wasm・JS 比較表 Wasm JS 実行速度 ◎ 速い △ 用途による

    DOM 操作 △ JS 経由 ◎ 得意 デバッグ △ 工夫が必要 ◎ DevTools 充実 言語選択肢 ◎ 多言語 △ JS/TS のみ ライブラリ △ Pure Go 中心 ◎ エコシステム豊富 使い分け: - 重い計算・既存資産の活用 → Wasm - UI 操作・軽量ロジック・エコシステム → JS - 両方を組み合わせるのがベストプラクティス
  40. ユースケースと使い分け Wasm・JS 比較表 Wasm JS 実行速度 ◎ 速い △ 用途による

    DOM 操作 △ JS 経由 ◎ 得意 デバッグ △ 工夫が必要 ◎ DevTools 充実 言語選択肢 ◎ 多言語 △ JS/TS のみ ライブラリ △ Pure Go 中心 ◎ エコシステム豊富 使い分け: - 重い計算・既存資産の活用 → Wasm - UI 操作・軽量ロジック・エコシステム → JS - 両方を組み合わせるのがベストプラクティス 計算のみに焦点を当てて比較を行った結果が 気になる方はぜひこちらもご覧ください - https://tech.every.tv/entry/2026/02/16/143650
  41. デモ - 処理の流れ - ユーザーが画像を選択 - JS: - 画像を Base64

    に変換 - JS: - Wasm: processImage() を呼び出し - Go(Wasm): - Base64 デコード → image.Decode で画像オ ブジェクト - RGBA キャンバスに描画 - Go のフォントライブラリでテキストを中央に描 画 - JPEG エンコード → Base64 で返却 - JS: - 返された Base64 を data URL にして <img> に表示 + ダウンロードリンク設置 https://github.com/keyl0ve/wasm-go-conference-mini-in-sendai-2026/tree/main/lgtm-generator
  42. まとめ - JS が苦手な処理は Go に寄せることができ高速化も見込める - ケースによるが Go の既存資産を簡単にブラウザに移植できる

    - Go は公式で手厚いサポートがある - Wasm にビルドする際は既存のビルドコマンドに対して環境変数を変えるだけで良い - wasm_exec.js も公式から提供されている ぜひ皆さんも Go・Wasm を使ってみてください! 今日スライドに出てきたコードやデモコード、 ebitengineを使ったゲームなど気になった方はご覧ください https://github.com/keyl0ve/wasm-go-conference-mini-in-sendai-2026
  43. 参考資料 - Go と Wasm の基本構成 - https://webassembly.org/ - https://qiita.com/ShuntaShirai/items/3ac92412720789576f22

    - https://dev.to/nabbisen/webassembly--3385 - https://golangtokyo.github.io/codelab/go-webassembly/?index=codelab#2 - https://go.dev/wiki/WebAssembly - https://tinygo.org/docs/guides/webassembly/wasm/ - https://html5experts.jp/chikoski/18964/ - https://developer.mozilla.org/ja/docs/WebAssembly/Guides/Concepts - https://www.assemblyscript.org/frequently-asked-questions.html#frequently-asked-questions - JS との連携 - https://developer.mozilla.org/ja/docs/WebAssembly/Guides/Loading_and_running - https://developer.mozilla.org/ja/docs/WebAssembly/Reference/JavaScript_interface/instantiateStreaming_static - https://pkg.go.dev/syscall/js - https://future-architect.github.io/articles/20250127a/ - https://zenn.dev/nobonobo/books/85e605893d44ebe7dd3f/viewer/b5ac64d9135e123e367a - デバッグとログ取得のコツ - https://github.com/golang/go/issues/63904 - https://developer.chrome.com/blog/wasmgc?hl=ja - ユースケースと使い分け - https://madewithwebassembly.com/ - https://github.com/hajimehoshi/ebiten - https://tech.every.tv/entry/2026/02/16/143650 - https://speakerdeck.com/goccy/go-de-webassembly-woli-yong-sita-shi-yong-de-napuraguinsisutemunogou-zhu-fang-fa - https://speakerdeck.com/syumai/go-webassembly-usecase-introductions