Wailsでデスクトップアプリを作る #2 プロジェクト構造と開発ループ — wails devとバインディング

読了 6分

前回はwails initでプロジェクトを作り、最初のウィンドウを開きました。今回はそのプロジェクトを構成するファイルを1つずつ開いていきます。ファイル数は少ないですが、それぞれの役割を正確に知っておけば、以降の回で機能を足すときにどこを直せばよいか迷わなくなります。

全 8 編(本編 6 編 + 応用 2 編)です。

  • #1 Wailsとは — Goで作る軽量デスクトップアプリ
  • #2 プロジェクト構造と開発ループ — wails devとバインディング ← この記事
  • #3 Goとフロントエンドの連携 — メソッドバインディングとイベント
  • #4 システム統合 — ダイアログ・メニュー・ウィンドウ制御
  • #5 実践機能 — 設定の保存とエラー処理
  • #6 ビルドと配布 — プラットフォーム別パッケージング

wails initが作ったもの #

まずプロジェクトルートの構造から確認します。

プロジェクト構造
hello-wails/
├── main.go          アプリのエントリーポイント — ウィンドウ設定と実行
├── app.go           フロントエンドに公開するGoコード
├── wails.json       プロジェクト設定(名前、ビルドコマンドなど)
├── go.mod           Goモジュール定義
├── build/           アイコン、プラットフォーム別ビルドリソース
└── frontend/        Webフロントエンド全体
    ├── index.html
    ├── src/
    ├── dist/        フロントエンドのビルド成果物
    └── wailsjs/     自動生成バインディング(直接編集しない)

役割分担は明確です。Go側のロジックはapp.goに、ウィンドウと実行の設定はmain.goに、画面はfrontend/に置きます。wails.jsonはプロジェクト名やフロントエンドのビルドコマンドといった設定を持ち、build/はアプリのアイコンとインストーラーのリソースが入る場所で、#6で再び登場します。

main.goを読み解く #

main.goはウィンドウを定義してアプリを実行するエントリーポイントです。

main.go — 核心部分
//go:embed all:frontend/dist
var assets embed.FS

func main() {
	app := NewApp()

	err := wails.Run(&options.App{
		Title:  "hello-wails",
		Width:  1024,
		Height: 768,
		AssetServer: &assetserver.Options{
			Assets: assets,
		},
		OnStartup: app.startup,
		Bind: []interface{}{
			app,
		},
	})
	if err != nil {
		println("Error:", err.Error())
	}
}

主なオプションは4つです。

  • Title、Width、Height — ウィンドウのタイトルと初期サイズです。
  • AssetServer — フロントエンドの成果物をアプリに内蔵して配信します。go:embedディレクティブがfrontend/distをバイナリの中に入れるため、単一実行ファイルでの配布が可能になります。
  • OnStartup — アプリ起動時に呼ばれる関数です。ここで受け取ったcontextが#3のイベントと#4のランタイム呼び出しで使われます。
  • Bind — フロントエンドに公開する構造体のリストです。バインディングの入口です。

app.go — バインディングの出発点 #

app.goにはフロントエンドから呼び出すコードが入ります。テンプレートが作ってくれる基本形はこうです。

app.go
type App struct {
	ctx context.Context
}

func NewApp() *App {
	return &App{}
}

func (a *App) startup(ctx context.Context) {
	a.ctx = ctx
}

func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s, It's show time!", name)
}

GreetのようにAppに付いた公開メソッドは、Bind設定を通じてフロントエンドから呼び出せる関数になります。規則と細かい動きは#3のテーマなので、ここでは「Goメソッドを追加するとフロントエンドの関数が1つ増える」という感覚をつかんでおけば十分です。

テンプレートの選択肢 #

wails init -tで指定するフロントエンドのテンプレートは好みで選べます。

テンプレート内容
vanilla / vanilla-tsフレームワークなしの素のHTML/JS(TS)
react / react-tsReact + Vite
vue / vue-tsVue + Vite
svelte / svelte-tsSvelte + Vite

Wailsから見ればフロントエンドは静的な成果物を出すWebプロジェクトにすぎないため、どのテンプレートを選んでもGo側のコードは同じです。このシリーズは次回からreact-tsテンプレートを基準に例を書いていきます。React基礎講座を終えた読者なら、フロントエンドのコードをそのまま読めます。

wails dev — 開発ループ #

開発中はwails devひとつでループ全体が回ります。

開発サーバーの実行
wails dev

動作の仕方はファイルの種類によって異なります。

  • フロントエンドの変更 — Viteのホットリロードがそのまま動き、保存した瞬間にウィンドウへ反映されます。
  • Goコードの変更 — Wailsが変更を検知してバックエンドを再ビルドし、アプリを再起動します。

ひとつ役に立つ事実があります。devモードでは同じアプリがブラウザでも開けます。

wails devの出力の一部
To develop in the browser and call your bound Go methods,
navigate to: http://localhost:34115

このアドレスをブラウザで開くとネイティブウィンドウと同じアプリが表示され、バインディングされたGoメソッドもそのまま呼び出せます。ブラウザ開発者ツールのコンソール、ネットワーク、要素検証をすべて使えるので、フロントエンドのデバッグはこちらが快適です。

ヒント
ネイティブウィンドウ自体でも、右クリックメニューやショートカットで開発者ツール(Inspect Element)を開けます。devモードでは標準で有効になっているので、ウィンドウ上で直接確認したいときに活用してください。

wailsjs — 自動生成されるバインディングコード #

テンプレートアプリで挨拶機能が動く経路をたどると、バインディングの正体が見えてきます。フロントエンドのコードはGoメソッドをこう使っています。

frontend/src/main.js — 呼び出す側
import { Greet } from '../wailsjs/go/main/App';

Greet(name).then((result) => {
    // result にGoが返した文字列が入る
});

frontend/wailsjs/ディレクトリは、wails devBindに登録された構造体を読んで自動生成したコードです。Goメソッド1つごとに同じ名前のJavaScript関数が作られ、呼び出しの結果はPromiseで返ってきます。手で編集するファイルではなく、Go側のメソッドを変えれば次のビルドで再生成されます。

まとめると開発ループはこう回ります。app.goにメソッドを追加するとwailsjsに関数が生まれ、フロントエンドでimportして呼び出すと結果がPromiseで返ってきます。この流れを実際の機能に広げるのが次回の内容です。

まとめ #

この記事の要点は3つです。

  • プロジェクトはmain.go(ウィンドウ・実行設定)、app.go(公開するロジック)、frontend/(画面)、wails.json(設定)に役割が分かれます。
  • wails devはフロントエンドのホットリロードとGoの再ビルドをまとめて処理し、ブラウザ(localhost:34115)でも同じアプリをデバッグできます。
  • frontend/wailsjsはBindのリストから自動生成されるバインディングコードで、GoメソッドをPromiseベースの関数として公開します。

次回の「Wailsでデスクトップアプリを作る #3 Goとフロントエンドの連携 — メソッドバインディングとイベント」では、バインディングの規則を正確に整理し、TODOリストのバックエンドを実際に作ってフロントエンドとつなぎます。Goからフロントエンドへデータをプッシュするイベントシステムもあわせて扱います。

X