🖥️

Wails v2のSvelteテンプレートで生成されるアプリの仕組み

に公開

Wailsとは?

Components of a Wails App

Wailsアプリの主要コンポーネント: 上図はWailsアプリの構成概念図です。Wailsでは、Go言語で書かれたバックエンド(下段の青緑色部分)と、Webビュー(Webkit等)を用いたフロントエンド(上段の赤色部分)を組み合わせてデスクトップアプリを構築します。Go側ではアプリケーションコードとランタイム(ウィンドウ操作などの機能提供)が動作し、フロントエンド側では組み込みのブラウザウィンドウ上にHTML/JS/CSSで構築したUIが表示されます。また、WailsではGoのメソッドをJavaScriptから呼び出せるよう「バインド」して公開する機能があり、これによりGoの関数をあたかもフロントエンドのJavaScript関数のように扱うことが可能です。

GoエンジニアにとってWailsを使うメリットは、OSごとのGUIツールキットを新たに習得しなくても、普段使っているGoとモダンなフロントエンド技術だけでクロスプラットフォームのデスクトップGUIアプリを作れる点です。仕組み自体はElectronに近く、Web技術でUIを構築しますが、バックエンドをGoで実装することでElectronよりもメモリ使用量が少なく実行ファイルも小さくなる利点があります。例えばWindowsではWebView2(Edgeベース)、macOSやLinuxではWebkitを内部利用しつつ、アプリのロジックはネイティブなGoコードで動作するため、パフォーマンスと配布のしやすさに優れています。

Wails v2 + Svelteテンプレートのプロジェクト構成

それでは、Wails v2でSvelteテンプレートを使って作成されるプロジェクト構成を見てみましょう。WailsのCLIコマンド wails init を使うとプロジェクトのひな形が自動生成されます。例えば Svelte を選択してプロジェクト名を指定する場合、以下のようにコマンドを実行します。

wails init -n MyApp -t svelte    # Svelte + JavaScript
# または TypeScript を使いたい場合
wails init -n MyApp -t svelte-ts # Svelte + TypeScript

実行すると、Goのモジュール初期化とSvelteのテンプレートを含んだフォルダ構成が作成されます。主なファイル・ディレクトリは次の通りです。

MyApp/
├── README.md
├── app.go
├── build
│   ├── README.md
│   ├── appicon.png
│   ├── darwin
│   │   ├── Info.dev.plist
│   │   └── Info.plist
│   └── windows
│       ├── icon.ico
│       ├── info.json
│       ├── installer
│       │   ├── project.nsi
│       │   └── wails_tools.nsh
│       └── wails.exe.manifest
├── frontend
│   ├── README.md
│   ├── dist
│   │   └── gitkeep
│   ├── index.html
│   ├── package.json
│   ├── src
│   │   ├── App.svelte
│   │   ├── assets
│   │   │   ├── fonts
│   │   │   │   ├── OFL.txt
│   │   │   │   └── nunito-v16-latin-regular.woff2
│   │   │   └── images
│   │   │       └── logo-universal.png
│   │   ├── main.ts
│   │   ├── style.css
│   │   └── vite-env.d.ts
│   ├── svelte.config.js
│   ├── tsconfig.json
│   ├── tsconfig.node.json
│   ├── vite.config.ts
│   └── wailsjs
│       ├── go
│       │   └── main
│       │       ├── App.d.ts
│       │       └── App.js
│       └── runtime
│           ├── package.json
│           ├── runtime.d.ts
│           └── runtime.js
├── go.mod
├── go.sum
├── main.go
└── wails.json

main.goGo側エントリポイント ): このファイルにはデスクトップアプリを起動するコードが記述されています。 main.go ではまず //go:embed ディレクティブを使ってビルド後のフロントエンド資産( frontend/dist 内のファイル群)を embed.FS として組み込み、 wails.Run(&options.App{ ... }) を呼び出します。例えばテンプレートのmain.goでは以下のような設定になっています。

// main.go(抜粋)
package main

import (
    "embed"
    "log"
    "github.com/wailsapp/wails/v2"
    "github.com/wailsapp/wails/v2/pkg/options"
    "github.com/wailsapp/wails/v2/pkg/options/assetserver"
)

// フロントエンドのビルド出力を埋め込む
//go:embed all:frontend/dist
var assets embed.FS

func main() {
    app := NewApp()  // App構造体のインスタンスを生成

    err := wails.Run(&options.App{
        Title:  "MyApp",             // ウィンドウのタイトル
        Width:  800, Height: 600,    // ウィンドウサイズ
        AssetServer: &assetserver.Options{Assets: assets}, // 埋め込んだ資産を利用
        OnStartup: app.startup,      // 起動時のコールバック
        Bind: []interface{}{ app },  // フロントに公開するGoオブジェクトを指定
    })
    if err != nil {
        log.Fatal(err)
    }
}

上記のように、 options.App 構造体でウィンドウのタイトルやサイズ、使用するフロントエンド資産、各種コールバック関数などを設定しています。ポイントとなるのは Bind オプションで、ここに渡された構造体(例では app )の公開メソッドがフロントエンドから呼び出せるようになります。テンプレートでは NewApp() で生成される App 構造体が定義されており、後述するようにこの中にバックエンドのロジック(例: Greet関数)があります。 OnStartup にはアプリ起動時に実行したい処理を登録できます(たとえば App.startup でコンテキストを保存したり初期データ読み込みを行う処理が書けます)。

wails.jsonプロジェクト設定 ): プロジェクトルートに生成されるJSONファイルで、Wailsビルドツール向けの各種設定を持ちます。 name(プロジェクト名)や frontend:dir (フロントエンドフォルダのパス、デフォルト "frontend" )のほか、フロントエンド資産をビルド・インストール・起動するためのコマンドが定義されています。例えばテンプレート生成直後の wails.json には次のようなキーが含まれます。

// wails.json(抜粋)
{
  "name": "MyApp",
  "frontend:install": "npm install",
  "frontend:build":   "npm run build",
  "frontend:dev":     "npm run dev"
}

これにより、Wailsのコマンド実行時に自動でフロントエンド用の npm コマンドが呼ばれるようになっています。必要に応じてパッケージマネージャ(例: yarnやpnpm)やビルドコマンドをここで調整できます。また assetdir (埋め込む資産ディレクトリパス)や devServerUrl(開発時に利用する外部サーバURL)等も指定可能ですが、通常Svelteテンプレートではデフォルト設定のままで問題ありません。

frontend/ディレクトリ(Svelteアプリ): ここにはSvelteで構築されたフロントエンドのコードと設定が入っています。基本的には「Vite + Svelte」で作られた標準的なWebフロントエンドプロジェクトであり、Node.jsの依存管理( package.json )、ビルド設定( vite.config.js )、ソースコード( src /以下)が含まれます。 index.html はアプリの土台となるHTMLファイルで、ここで <div id="app"> のようなマウントポイントが用意され、 src/main.ts (または main.js )がその要素に対してSvelteのAppコンポーネントをマウントする形になっています。Svelteの単一ファイルコンポーネントである App.svelte が画面の主要UIを定義し、必要に応じて src/lib/ に追加のコンポーネントや、 src/assets/ に画像などのアセットも配置されます。テンプレートにはWailsのロゴ画像( src/assets/images/logo-universal.png )や簡単なカウンター用のコンポーネント( Counter.svelte )が含まれており、これらはSvelteの使い方とプロジェクト構成に慣れるサンプルとなっています。

wails init 後のビルドと開発 ( wails dev )

プロジェクトを生成したら、開発を開始するために wails dev コマンドを実行します。 wails dev はWailsアプリを開発モードで起動するためのコマンドで、以下の処理を自動的に行います:

  • バックエンドとフロントエンドのビルド・起動: Goコードをビルドしてアプリケーションを実行し、同時にフロントエンドの開発サーバーも起動します(初回実行時には npm install が走り、必要な依存がインストールされます)。
  • Goメソッドのバインド: 起動時に main.go で指定されたGo側のメソッド群をフロントエンド(JavaScript)にバインドし、呼び出せるように準備します。
  • ファイル変更の監視とホットリロード: Viteの機能によりフロントエンド資産が変更されれば即座にHMR(ホットリロード)でUIが更新され、さらにWailsはGoコードの変更も監視して再ビルド・再実行を行います。コードを書き換えて保存するだけでアプリが自動更新されるため、開発サイクルが高速です。
  • 開発用サーバーの起動: デスクトップアプリのウィンドウを表示するとともに、内部的にはローカルホスト上に開発用のWebサーバーも立ち上がります(デフォルトではポート34115)。このサーバーにより、必要に応じて通常のWebブラウザで http://localhost:34115 にアクセスしてデバッグすることも可能です。ブラウザの開発者ツールを使ってUI要素を調査したり、コンソールからバインド済みのGo関数を呼び出すこともできます。

開発モードでは、Wailsは埋め込み済みの資産ではなく frontend/ 内の現在のファイル群をそのまま読み込んで利用します(ビルド不要)。したがってフロントエンドについてはViteの開発サーバー経由で常に最新の状態が表示され、Goについても変更があるたびにバックエンドが再起動されます。wails devを実行すると自動でデスクトップアプリのウィンドウが現れ、テンプレートの画面(Wailsロゴやカウンター等のUI)が表示されるはずです。以降はそのウィンドウを開いたまま、コードを編集して保存すると即座にリロードされるので、フロントとバックエンドが正しく連携しているか確認しながら開発を進められます。

補足

⚠️ 補足: 開発を始める前に、Goだけでなくフロントエンドビルドのために必要なNode.jsおよびnpm(またはyarn等)がインストールされていることを確認してください。 wails init で生成されたプロジェクトは内部でnpmスクリプトを利用しています。 wails dev 実行時に自動的に npm install が走りますが、事前に環境が整っていないと依存関係の取得や開発サーバー起動ができない場合があります。

フロントエンド(Svelte)とバックエンド(Go)の連携

それでは、Wailsを介してSvelteのフロントエンドとGoのバックエンドがどのように通信しているのかを見てみましょう。ポイントはメソッドのバインディングです。先述の通り、 main.goBind: []interface{}{ app } とすることで、 App 構造体の公開メソッドがフロントエンドから呼び出せるようになります。Wailsはアプリ起動時にバインド対象の構造体を走査し、エクスポートされたメソッド(頭文字が大文字の関数)について自動的にJavaScript側に呼び出しコードを生成します。デフォルトのテンプレートでは、App構造体(app.go等に定義)に以下のようなメソッドが用意されています。

// App.go(抜粋)
type App struct {
    // ...(省略:必要に応じてコンテキストや状態を保持)
}

// Greetは指定された名前に対する挨拶文を返す
func (a *App) Greet(name string) string {
    return fmt.Sprintf("Hello %s!", name)
}

上記のようなメソッドがあると、 wails dev 実行時にWailsはこの Greet 関数をフロントエンドへエクスポートします。具体的には、プロジェクトのfrontend/wailsjs/ディレクトリ以下に自動生成されたJSモジュールが出力され、そこにGreetを呼び出す関数が定義されます。ファイルパスはGoのパッケージ名と構造体名に対応しており、例えば App 構造体が main パッケージにある場合、 frontend/wailsjs/go/main/App.js というモジュールが生成されます。このモジュールをインポートすると、Goの App.Greet を呼び出すためのJavaScript関数 Greet(…) が利用できます。

フロントエンドのSvelteコード側では、その自動生成された関数を使ってGoのメソッドを呼び出します。テンプレートに含まれる App.svelte の例を見てみましょう(名前を入力して挨拶メッセージを表示する簡単なUIです)。

<script lang="ts">
  import logo from './assets/images/logo-universal.png'
  import {Greet} from '../wailsjs/go/main/App.js'

  let resultText: string = "Please enter your name below 👇"
  let name: string

  function greet(): void {
    Greet(name).then(result => resultText = result)
  }
</script>

<main>
  <img alt="Wails logo" id="logo" src="{logo}">
  <div class="result" id="result">{resultText}</div>
  <div class="input-box" id="input">
    <input autocomplete="off" bind:value={name} class="input" id="name" type="text"/>
    <button class="btn" on:click={greet}>Greet</button>
  </div>
</main>

上記コードでは、まず ../wailsjs/go/main/App.js から { Greet } 関数をインポートしています。この Greet 関数こそ、Wailsが生成したGoの App.Greet メソッドのフロントエンド版です。 greet() 関数内で Greet(name) を呼ぶと、内部的にはバックエンドのGoの関数が実行され、その戻り値が Promise 経由で返ってきます。JavaScriptではこのように 非同期関数呼び出し として結果を受け取るため、上記のように .then(...) でハンドリングするか、 async/await 構文を用いても良いでしょう。この例では、テキストボックスで入力した名前をGo側に送り、ボタン押下で Greet を呼び出して、その結果の挨拶文(例えば「Hello Taro!」)を画面上の {resultText} に表示しています。Goで処理したロジックの結果をそのままUIに反映できるのがWailsの便利なところです。

補足

ℹ️ 補足: WailsにはGoからフロントエンドへデータやイベントを送信する機能もあります。例えばバックエンドで定期的なイベントを発生させたい場合、Go側で runtime.EventsEmit(ctx, "tick", data) のようにイベント送信し、フロントエンドでは import { EventsOn } from "../wailsjs/runtime/runtime.js"; として EventsOn("tick", (data) => { ... }) で受け取ることができます。これにより、バックエンド主導でフロントエンドの状態を変化させるリアルタイム連携も実現できます。こちらは応用的な機能ですが、必要に応じて公式ドキュメントを参照してみてください。

まとめ

Wails v2のSvelteテンプレートで自動生成されたアプリを例に、その構成と動作について解説しました。Go製のバックエンドとSvelte製のフロントエンドがWailsによって結び付き、 wails dev コマンド一つでホットリロード付きの開発環境が立ち上がるのは、生産性の高い開発体験です。Go開発者であれば、馴染みのあるGo言語でビジネスロジックやシステム処理を実装しつつ、同じプロジェクト内で最新のWebフロントエンド技術を活用してリッチなUIを構築できる点は大きな魅力でしょう。

完成したアプリは wails build コマンドでビルドできます。ビルド時にはフロントエンドがバンドルされ frontend/dist に出力、それがGoバイナリに埋め込まれて単一の実行ファイル(exeやAppバンドル)が生成されます。外部依存ファイルのないシングルバイナリとして配布可能で、ユーザにとってインストールが容易なデスクトップアプリとなります。Wailsはまさに「Goとモダンなフロントエンドの架け橋」と言えるフレームワークです。ぜひ実際に手を動かして、Go+Svelteで快適なアプリ開発を体験してみてください。

Discussion