🦜

GoとWails+SveltKitでGUIを始めよう!

2023/10/15に公開
1

https://zenn.dev/nobonobo/articles/6cc4c510988e82#ユーザー定義テンプレート

ここで紹介したWailsの実際の始め方をまとめてみます。

あらかじめ必要なもの

  • go(version 1.21.x以降)
  • npm(version 10.x.x以降)

Wailsコマンドラインツールのインストール

go install github.com/wailsapp/wails/v2/cmd/wails@latest

これでPATH環境変数に${GOPATH}/binが含まれていれば、「wails」コマンドが利用できるようになります。

プロジェクトの作成

拙作のテンプレートを使ったプロジェクト開始方法。

wails init -n sample -t https://github.com/nobonobo/wails-sveltekit

プロジェクトの開発モード起動

cd sample
wails dev

初回はnpm経由でツールのインストールが走ったりするのでちょっと時間がかかります。

以下のようなウインドウが開けば成功です!

フロントエンド編集

frontend/src/routes/+page.svelteを編集してみましょう。

<h1>Welcome to SvelteKit</h1>

上記のところを以下のように変更して保存してみましょう。

<h1>Welcome to SvelteKit!!</h1>

すると以下のようにさっとリロードが走って即座に反映されます。

Go実装の編集

app.goがユーザーが主に実装を入れるところですが、これらGoのコードを編集し、保存した時にもリビルドが走ります。

これは、すこし時間がかかったのちウインドウが一旦消えて少し後に再表示されます。

app.go
	return fmt.Sprintf("Hello %s!", name)

上記を以下のように変更してみましょう。

app.go
	return fmt.Sprintf("Hello %s!!!", name)

再表示されたウインドウのボタン「greet」をクリックしてみましょう。

ちゃんと反映された結果が表示されますね!

Go実装がフロントエンドから呼び出せる仕掛け

WailsはGo側のApp構造体定義のPublicメソッドなどをJavaScriptやTypeScriptから呼べるようにするスタブをコード生成してくれます。

生成コードのデフォルトの出力先はfrontend/src/lib/wailsjs/の下にあるgo/main/App.jsです。なのでそのモジュールの参照パスは../lib/wailsjs/go/main/App.jsというパスをimportすることで利用可能です。

Go側の実装

app.go
// Greet returns a greeting for the given name
func (a *App) Greet(name string) string {
	return fmt.Sprintf("Hello %s!!!", name)
}

Svelteテンプレート上のコード

+page.svelte
<script>
  import { Greet } from "$lib/wailsjs/go/main/App.js";
  let greet = await Greet("Hoge")
</script>

WebSocket経由のリモートプロシージャコールで呼び出され、結果はPromiseオブジェクトなのでawaitで受ける必要があります。

イベントの送受信

Wailsランタイムに用意されたイベント送受信の仕組みもあります。

JavaScript側にはグローバルに定義済みのruntime(インポート済み)から、Go側はgithub.com/wailsapp/wails/v2/pkg/runtimeをインポートすることで利用可能です。

ランタイムのさまざまな機能についてはここにリファレンスがあります。

https://wails.io/ja/docs/reference/runtime/intro/

runtime.EvensOn(...)で受信側、runtime.EventsEmit(...)で送信側です。

ただ、一部の機能にはブラウザのwindowを必要とするものがあり、Svelte特有の問題として、コンパイル時にブラウザコンテキストじゃない状況でロードしてしまうのでモジュールトップレベルでそれらの機能を利用しようとするとコンパイルエラーになってしまいます。

それを回避する場合に以下のようなガードが必要です。

import { browser } from "$app/environment";
if (browser) {
  runtime.EventsOn(...);
}

Go側実装サンプル

app.go
// startup is called at application startup
func (a *App) startup(ctx context.Context) {
	// Perform your setup here
	a.ctx = ctx
	go func() {
		ticker := time.NewTicker(3 * time.Second)
		cnt := 0
		for range ticker.C {
			cnt++
			runtime.EventsEmit(ctx, "count", cnt)
		}
	}()
}

Go側のランタイムの機能利用には第一引数にcontext.Contextインスタンスが必要です。

JS側実装サンプル

+page.svelte
<script>
  import { browser } from "$app/environment";
  let Count = 0;
  if (browser) {
    runtime.EventsOn("count", (cnt) => (Count = cnt));
  }
</script>

この例はGo実装からJS実装へイベントを送信する向きの形ですが、逆向きも実装可能です・・・が、App構造体にPublicメソッド生やしてそれを呼ぶ方が手っ取り早いですね。

バイナリビルド

wails build

以上のコマンドにてbuild/bin配下にGUIアプリケーションのファイルが出力されています。

まとめ

- プロジェクト作成から簡単にGUIアプリケーションがビルドできる
- HTMLノウハウでGUIをつくれる
- SvelteKitのシンプルなつくりとGoはマッチしている(気がする)
- フロントもネイティブもソースの変更が自動的に反映されるホットリロード機能がある
- フロント実装とネイティブ実装との架け橋には必要十分なランタイムが用意されている
- WebUI方式だと、ネイティブとフロントの疎通が面倒という悩みがあったけど解消されている
- 配布アプリケーションも出力できる
- macOS、Linux、Windows向けのアプリケーションも同じコードから出力できる
- upxによる圧縮出力もオプションで選択可能
- Windows向けビルドの場合、インストーラーを生成する機能もあるらしい

追記

https://github.com/nobonobo/wails-sveltekit-skeleton

こっちはSvelte+Tailwind.cssによるUIフレームワークSkeletonをバンドルしたテンプレートです。

初期画面

Skeletonの豊富なコンポーネントがさくさく使えるプロジェクトが簡単に始められます!

現状わかってる問題

  • Light/Darkモードが動的実行の時はOSの設定に従うが、staticビルドした場合、OSの設定に依らずLightモードが初期状態になってしまう
  • SkeletonのLightSwitchコンポーネントを置いておくことでこのモードをコントロールできるので隅っこに置いておこう
  • app.goにて"syscall/js"を利用してはいけません
  • MPAする場合、app.htmlのheadに以下の記述を追加
  • SkeletonのFormsを使うときはtailwindcssのプラグインにformsを追加する必要あり
    <meta name="wails-options" content="noautoinject" />
    <script src="/wails/ipc.js"></script>
    <script src="/wails/runtime.js"></script>

Discussion