wails について
Go バックエンドとプラットフォームの WebView フロントエンドでデスクトップアプリを構築するための Wails ガイドです。安定版 v2 とアルファ版 v3 を扱い、互換性のない API の混在を避けるため、まずバージョンを確認します。
主な機能
- 01Go と Web フロントエンドのデスクトップアプリを解説
- 02Wails v2 と v3 を個別に説明
- 03バージョン判定と対応 API を案内
- 04バインディング、イベント、ビルドを解説
- 05v3 の MCP 組み込み機能を説明
活用シーン
- 01新規プロジェクトの Wails バージョン選択
- 02v2 と v3 の API 混在の診断
- 03Go とフロントエンドのバインディング設計
- 04Wails MCP によるアプリテスト
MCP 対応
Wails v3 は WAILS_MCP=1 でビルドすると、MCP サーバーをアプリに組み込めます。MCP クライアントからウィンドウ操作、DOM 検査、バインドされたメソッドの呼び出しができます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ spf13/ go-skills --skill wailsインストール後、Claude Code で/wailsを入力して読み込まれたか確認します。
同梱ファイル.claude-plugin/plugin.json
詳細
Wails:Go でデスクトップアプリを構築
Wails は、Go バックエンドと、プラットフォームの WebView(macOS/Linux では WebKit、Windows では WebView2)でレンダリングされる Web フロントエンドを使って、ネイティブのデスクトップアプリケーションを構築します。Chromium は同梱されないため、バイナリのサイズは数百メガバイトではなく、数メガバイトです。
互換性のない API を持つ 2 つのメジャーバージョンが共存しています。 v2 は安定版です。v3(2026 年半ば時点)はアルファ版の後期段階です。API はかなり安定しており、本番アプリもリリースされていますが、リリースは夜間ビルドの v3.0.0-alpha2.x タグで、詳細はまだ変更される可能性があります。特に LLM で最もよく見られる失敗は、v2 と v3 のコードを混在させることです。すべての回答は、v2 か v3 のどちらか一方だけにしてください。
ステップ 0 — バージョンを特定する(必須)
コードを書く前に、プロジェクトからバージョンを特定してください。ユーザーの表現から推測してはいけません。
| シグナル | v2 | v3 |
|---|---|---|
| go.mod のインポート | github.com/wailsapp/wails/v2 | github.com/wailsapp/wails/v3 |
| CLI | wails(wails dev、wails build) | wails3(wails3 dev、wails3 build) |
| ビルドのオーケストレーション | 不透明で、wails.json によって制御 | Taskfile.yml(go-task)を使用し、透明性が高い |
| エントリーポイント | wails.Run(&options.App{...}) | application.New(application.Options{...}) |
| Go → JS の公開 | Bind: []interface{}{app} | Services: []application.Service{...} |
| ランタイム呼び出し | コンテキストを保存して runtime.EventsEmit(ctx, ...) | app / window のメソッドを使用 — コンテキストの受け渡しは不要 |
| フロントエンドのインポート | wailsjs/go/main/App | frontend/bindings/<import-path>/<service> |
まだコードのない新規プロジェクトの場合: 安定性と変更の少なさを重視するなら、デフォルトで v2 を選んでください。アルファ版であることを許容できる長期運用の新規アプリには v3 を推奨します。アーキテクチャが優れており(マルチウィンドウ、サービス、透明性の高いビルド)、今後の主流になるためです。トレードオフを 1 文で説明し、go.mod には正確なアルファ版のバージョンを固定してください。v3 を安定版として紹介してはいけません。
既存コードに両方の列の API が含まれている場合、 それが不具合です。他の作業に着手する前に指摘してください。
Wails v2(安定版)
標準的な main.go
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := NewApp()
err := wails.Run(&options.App{
Title: "myapp",
Width: 1024,
Height: 768,
AssetServer: &assetserver.Options{Assets: assets},
OnStartup: app.startup,
Bind: []interface{}{app},
})
if err != nil {
log.Fatal(err)
}
}
embed ディレクティブは all:frontend/dist です。Vite が先頭にアンダースコアの付いたファイルを出力する場合、それらを含めるには all: プレフィックスが必要です。
コンテキストを取得するパターン
v2 のランタイムで必要なコンテキストはすべて、OnStartup に渡されます。これを保存してください。これは、すべての v2 アプリを支える基本的な慣用パターンです。
type App struct {
ctx context.Context
}
func (a *App) startup(ctx context.Context) {
a.ctx = ctx // required for all runtime calls
}
func (a *App) OpenFile() (string, error) {
return runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{})
}
OnStartupが呼び出される前にruntime.*を呼び出してはいけません — まだコンテキストが存在しません。これは v2 でよくある起動時クラッシュの原因です。- ここでの
runtimeはgithub.com/wailsapp/wails/v2/pkg/runtimeです — ウィンドウ制御、ダイアログ、イベント、クリップボード、メニューを扱います。
バインディングとイベント
- バインドされた構造体のエクスポート済みメソッドは、
wailsjs/go/main/Appの下に非同期の JS 関数として生成されます。Go の(value, error)戻り値は、解決または拒否される Promise になります。 - Go → JS:
runtime.EventsEmit(a.ctx, "name", data)を使用します。JS 側ではwailsjs/runtimeのEventsOn("name", cb)を使用します。 - バインドする構造体は薄いアダプターにしてください。ビジネスロジックは、Wails のインポートを一切含まない独立したパッケージに置きます(cobra-viper スキルの
cmd/と同じルールです)。
v2 でよくある間違い
- 起動前にランタイム関数を呼び出す(コンテキストが nil または無効)。
- embed ディレクティブに
all:を付け忘れる — 本番ビルドでアセットがひそかに欠落します。 - 生成された
wailsjs/ファイルを編集する —wails dev/wails buildのたびに再生成されます。 - 構造体を値でバインドする(
Bind: []interface{}{App{}})— 状態が保持されるよう、ポインターをバインドしてください。
Wails v3(アルファ版)
標準的な main.go
//go:embed all:frontend/dist
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "myapp",
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(assets),
},
Services: []application.Service{
application.NewService(NewGreetService()),
},
})
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "myapp",
})
if err := app.Run(); err != nil { // blocks; must run on the main goroutine
log.Fatal(err)
}
}
Manager API スタイルを使用してください — app.Window.NewWithOptions、app.Event.On、app.Menu.New()、app.SystemTray.New()。初期のアルファ版ではフラットなメソッド(app.NewWebviewWindowWithOptions、app.OnEvent)が使われていました。チュートリアルや LLM の学習データには、それらが多数含まれています。メソッドが見つからない場合は、manager 配下に移動した可能性があります。推測で作り出すのではなく、変更履歴を確認してください。
サービス:v3 のバインディングモデル
サービスは v2 の Bind に代わるものです。サービスは単純な構造体で、エクスポートされたメソッドがバインドされます。また、任意のライフサイクルメソッドを2つ実装して、アプリにフックできます。
type NoteService struct {
db *sql.DB
mu sync.RWMutex
}
func (s *NoteService) ServiceStartup(ctx context.Context, opts application.ServiceOptions) error {
// open resources, run migrations, start background goroutines governed by ctx
return nil // returning error aborts app startup
}
func (s *NoteService) ServiceShutdown() error {
return s.db.Close() // services shut down in reverse registration order
}
func (s *NoteService) Save(n Note) error { ... } // callable from JS
- サービスはすべてのウィンドウで共有されるシングルトンです。可変状態はミューテックスで保護してください。
- 依存関係は通常のコンストラクターを通じて渡します(
application.NewService(NewNoteService(db, logger)))。DI フレームワークは不要です。 - サービスは
http.Handlerを実装し、application.NewServiceWithOptions(svc, application.ServiceOptions{Route: "/files"})に登録することで、HTTP に対応できます。 - v2 と同じく、サービスは薄いアダプターです。ドメインロジックはテスト可能なように、Wails に依存しないパッケージに置きます。
バインディング、イベント、ウィンドウ
wails3 generate bindingsでフロントエンドのバインディングを生成します(wails3 devの実行時に自動生成されます)。wailsjs/ではなく、frontend/bindings/<full-go-import-path>/<service>からインポートしてください。- イベント:
app.Event.Emit("name", data)/app.Event.On("name", handler)。OS レベルのイベントはapp.Event.OnApplicationEvent(events.Common.ApplicationStarted, ...)を使います。キャンセル可能なフック:window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent) { e.Cancel() })。これは標準的な「閉じる前に確認する」パターンです。 - マルチウィンドウは標準機能です。必要な数の
WebviewWindowを作成でき、それぞれを個別に設定できます。
ビルドシステム
v3 のビルドは Taskfile.yml を介して go-task が調整します。アイコン生成、マニフェスト、パッケージ化は、魔法のように隠された処理ではなく、確認・編集可能なタスクステップです。CLI と格闘するのではなく、タスクを編集してカスタマイズしてください。プラットフォームごとのパッケージ化には wails3 package を使います(macOS では .app/dmg、Windows では NSIS、Linux では AppImage/deb)。
エージェント機能の追加要素:WAILS_MCP=1 を指定してビルドすると MCP サーバーが埋め込まれ、LLM エージェント(Claude を含む)が実行中のアプリを操作できます。ウィンドウの制御、DOM の検査、バインドされたメソッドの呼び出しが可能で、開発中の自動テストに非常に役立ちます。
v3 アルファ版での注意点
- go.mod でアルファ版のバージョンを固定し、変更履歴(
v3.wails.io/changelog)を確認しながら、意図的にアップグレードしてください。ナイトリーアルファ版では API 名が変更されることがあります。 - v3 の API 呼び出しがコンパイルエラーになり、修正方法が明らかでない場合は、推測する前に最新のドキュメントを確認してください。学習データは意図的にアルファ版に追いついていません。
バージョン混在の兆候(見つけたら却下)
- v3 プロジェクトでの
runtime.EventsEmit(ctx, ...)またはpkg/runtimeのインポート → v2 の API です。app.Event.Emitを使ってください。 - v2 プロジェクトでの
application.New(...)またはpkg/application→ v3 の API です。wails.Run(&options.App{...})を使ってください。 - v3 でのフロントエンドからの
wailsjs/go/...のインポート(正しくはfrontend/bindings/...)、またはその逆。 - v3 プロジェクトで
wails devを実行する、または v2 でwails3 devを実行する。 application.Options内のBind:フィールド、またはoptions.App内のServices:。それぞれ、対応するバージョンでのみ使用します。
共通の原則(両バージョン)
- フロントエンドからの入力は信頼できません。バインドされたメソッドの引数は HTTP ハンドラーと同じように検証してください。フロントエンドから渡された文字列をそのまま使って、シェルコマンドやファイルパスを組み立てないでください。
- バインドされたメソッドからエラーを返してください(エラーメッセージを含む Promise の拒否になります)。パニックを起こしたり、エラーを握りつぶしたりしないでください。
- シークレットは Go 側に保管してください。
frontend/distに含まれるものはすべてユーザーが読み取れます。 - バインドされたメソッドで時間のかかる処理を実行すると、UI ではなく JS の Promise がブロックされます。ただし、フロントエンドからポーリングさせるのではなく、進捗イベントを発行してください。