🦜

GoでクロスプラットフォームGUI(2022)

2021/12/18に公開
2

本記事はGo Advent Calendar 2021その3の19日目の記事になりますー。

いくつか試した結果

ライブラリ名 ベース 更新 ビルド 出力 IME Windows Mac Linux iOS Android
therecipe/qt Qt 遅め 遅い
kitech/qt.go Qt 停滞 早い ×
lxn/walk Native 活発 早い × × × ×
fyne-io/fyne GLFW 活発 早い ×
zserge/lorca Chrome 停滞 早い × ×
andlabs/ui Native 遅め 早い × ×
wailsapp/wails WebView 活発 早い × ×

andlabs/ui、lxn/walkは簡素なウィジェットセットだけをサポート。fyne、Qtベースは豊富なウィジェットセットを持つ。いずれも少人数でメンテしていることを考えると、コンパクトなツールキットで活発な活動中のものが品質面でおすすめです。

  • IMEサポートの有無は日本語圏でのGUI提供において重要なファクター
  • 英語圏生まれのGUIライブラリの多くはIMEサポートをあまり考慮していない
  • GLFWはIMEサポートのPRが何年も取り込まれない状況がつづいている
  • HTMLベース(ChromeやWebView)ならIMEサポートを内包している
  • 自作系はどうしてもギョッとする挙動や細かく期待しない挙動にちょくちょく出会ってしまう(例えば日本語のワードラップ未対応など)
  • HTML系はUIを構築していく上でのほとんどのケースで細かい問題に対する解決策がちゃんとある

クロス環境対応ということであればzserge/lorcaもよさそうで、こちらは以前記事を書きました。

https://zenn.dev/nobonobo/articles/86e7d159ce2a22

また、therecipe/qtについてはだいぶ前に記事を書きました。

http://golang.rdy.jp/2016/10/20/how2make-gui/

therecipe/qtの特徴

  • デスクトップでもモバイルでもGUIアプリを構築できる
  • Qtそのものには多くのメリットがあり、そのメリットを壊さない様にラップしたライブラリ
  • ただ、そのメリットを得るためのノウハウを理解するのに大きなコストがかかる
  • Qtのモダンな設計では実質JSのようでJSではない環境でのプログラミングを学ぶ必要がある
  • Go環境とJSライク環境の間を取り持つブリッジの実装に骨が折れる
  • コンパイルはGoのコンパイルとC++のコンパイルの両方を行うため時間が必要
  • 成果物は大量の依存ランタイムファイルとGoアプリが生成されてサイズは大きい

kitech/qt.goは上記のコンパイルの遅さを改善しようとしたものです。その他GLFWベースのGUIライブラリもコンパクトなものからfyneのように巨大なものまでたくさんあるのですが、如何せんGLFWはIMEサポートがなかなか取り込まれないというツラみがあります。andlabs/uiは単画面のシンプルなUIを作るのには向いていると思います。

wailsapp/wailsはv2になり超絶便利かつGo1.16対応しました。しかもかなり更新も活発ということでこの記事ではWails/v2について紹介してみようと思います。

Wails/v2

  • HTML/WebViewベースのアプリツールキット
  • ただし、WebViewやダイアログ、メニューはNativeの機能を利用する
  • 2021/12現在v2ベータが最新
  • 最近v2になってプロジェクト管理用ユーティリティがバンドルされ便利になった
  • 各デスクトップOS向けのアイコンバンドラーも付属
  • frontend部分はnpmベースで構築し、リリース時にgo:embedでバンドルされる
  • wails本体はコンパクトかつシンプル(fyneやQtに比べて)
  • 成果物のロード時間やメモリ消費量はElectron系に比べると少ない
  • GoのApp実装のメソッドはJavaScriptから呼び出せる様になっている
  • 各種イベントのコールバックをGo実装で定義することも可能
  • Linuxサポートは準備中っぽい?

以下はWails/v1用のサイトです。

  • v1をチョイスする理由の一つはLinuxをサポートしていること <- v2がLinuxサポートされました!

プロジェクト作成

wailsコマンドのインストール

% go install github.com/wailsapp/wails/v2/cmd/wails@latest
% wails init -n sample -t svelte
Wails CLI v2.0.0-beta.20


Initialising Project Sample
---------------------------


Project Name:      sample
Project Directory: /Users/nobo/uitest/sample
Project Template:  svelte
Template Support:  https://github.com/wailsapp/wails

Initialised project 'sample' in 794ms.


If Wails is useful to you or your company, please consider sponsoring the project:
https://github.com/sponsors/leaanthony

% cd sample

初期状態のプロジェクトファイルツリー

sample/
├── README.md
├── app.go
├── build/
│   ├── README.md
│   ├── appicon.png
│   ├── darwin/
│   │   └── Info.plist
│   └── windows/
│       ├── icon.ico
│       └── sample.exe.manifest
├── frontend/
├── go.mod
├── go.sum
├── main.go
└── wails.json

app.go,main.goがアプリケーション実装で、frontend/以下にフロントエンド実装が入っています。

build

% wails build
Wails CLI v2.0.0-beta.20



App Type: 		desktop
Platform: 		darwin
Arch: 			amd64
Compiler: 		/usr/local/bin/go
Skip Frontend: 		false
Compress: 		false
Package: 		true
Clean Build Dir: 	false
LDFlags: 		""
Tags: 			[]

Installing frontend dependencies: 
Done.
Compiling frontend: Done.
Compiling application: Done.
Packaging application: Done.

Built '/Users/nobo/uitest/sample/build/bin/sample.app' in 1m26.242s.


If Wails is useful to you or your company, please consider sponsoring the project:
https://github.com/sponsors/leaanthony

とするとmacOSの場合、build/bin/sample.appに出力されます。

起動

% open build/bin/sample.app


(OSネイティブのWebViewなので漢字入力も問題ないですね!)

出力ファイルは以下の通り極めてシンプルです。

sample.app/
└── Contents/
    ├── Info.plist
    ├── MacOS/
    │   └── sample
    └── Resources/
        └── iconfile.icns

依存ライブラリは以下の様にmacOSのGUIアプリとして最小限になっています。

% otool -L build/bin/sample.app/Contents/MacOS/sample
build/bin/sample.app/Contents/MacOS/sample:
	/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1856.105.0)
	/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 23.0.0)
	/System/Library/Frameworks/WebKit.framework/Versions/A/WebKit (compatibility version 1.0.0, current version 612.3.6)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
	/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1856.105.0)
	/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60157.60.19)
	/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 2113.20.111)

アイコンもちゃんと付与されています

クロスビルド

macOS上でWindows版のバイナリをビルドするのも簡単です。

% wails build -platform windows
Wails CLI v2.0.0-beta.20



App Type: 		desktop
Platform: 		windows
Arch: 			amd64
Compiler: 		/usr/local/bin/go
Skip Frontend: 		false
Compress: 		false
Package: 		true
Clean Build Dir: 	false
LDFlags: 		""
Tags: 			[]

Installing frontend dependencies: Done.
Compiling frontend: Done.
Generating bundle assets: Done.
Compiling application: Done.

Built '/Users/nobo/uitest/sample/build/bin/sample.exe' in 23.402s.


If Wails is useful to you or your company, please consider sponsoring the project:
https://github.com/sponsors/leaanthony

Windows向けのバイナリはbuild/bin/sample.exeに出力されます。注意が必要なのはWindows10以降の64bitバイナリ対象で、32bitビルドはサポートしていない様です。

開発モード

以下のコマンドで開発モードを起動できます。-eオプションにてウォッチするファイル拡張子を指定でき、ウォッチ対象のファイルが変更されたらWebViewを再構築します。

wails dev -e go,svelte,html
Wails CLI v2.0.0-beta.20


Executing: go mod tidy
Executing: wails generate module
Building application for development...
Installing frontend dependencies: Done.
Compiling frontend: Done.
Compiling application: Done.

Watching (sub)/directory: /Users/nobo/uitest/sample
Using Dev Server URL: http://localhost:34115
Using reload debounce setting of 100 milliseconds
DEB | [DesktopAssetServer] Loading assets from: /Users/nobo/uitest/sample/frontend/dist
DEB | [DevWebServer] Serving assets from: /Users/nobo/uitest/sample/frontend/dist
DEB | [DevWebServer] Serving application at http://localhost:34115
DEB | [DesktopAssetServer] Loading file from disk: index.html
DEB | [DesktopAssetServer] Loading file from disk: /global.css
DEB | [DesktopAssetServer] Loading file from disk: /bundle.js
DEB | [DesktopAssetServer] Loading file from disk: /assets/images/logo-dark.svg
DEB | [DesktopAssetServer] Loading file from disk: /assets/fonts/nunito-v16-latin-regular.woff2

ユーザー定義テンプレート

-tオプションでリポジトリURLを指定すると、独自のテンプレートを利用してプロジェクトを新規作成することができます。

ここにSvelteKit版のテンプレートを作って公開しました。参考にしてください。

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

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

サンプルをつくってみる

以下の依存で構築してみるよ!

% wails init -n sample -t https://github.com/nobonobo/wails-sveltekit
% cd sample
% curl https://raw.githubusercontent.com/picocss/pico/aca3ce18089ab18dafb755162bc0389f07e98831/css/pico.min.css -o frontend/static/pico.min.css

app.htmlに改変を加えます。

  • pico.min.cssスタイルシートを読み込む
  • コンテンツを<main class="container">で囲む
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="description" content="" />
    <link rel="icon" href="/favicon.png" />
    <link rel="stylesheet" href="/pico.min.css" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    %svelte.head%
  </head>
  <body>
    <main class="container">
      <div id="svelte">%svelte.body%</div>
    </main>
  </body>
</html>

余談: picocssの特徴

  • OSのノーマル/ダークモードに追従するテーマをデフォルトで持っている
  • 省サイズで基本を網羅している
  • ドキュメントがしっかりある

IPCブリッジ

JSコンテキスト側にはwindow.goというインスタンスが拡張されています。go.main.AppインスタンスはGo側AppインスタンスのIPCスタブになっています。

例えばApp構造体のGreetメソッドは以下のように呼び出せます。

let res = await go.main.App.Greet("hello");

もちろん、これでDOMイベントのハンドリングすることもできます。

実績

まとめ

  • 今回の最小サンプルでmacOSで5.2MB、Winで7MB程度の出力
  • ファイル数も最小化され配布もしやすい
  • ネイティブのWebViewを起動するだけなので起動も早い
  • 同じコードからmacOS用、Windows用のGUIアプリケーションを構築できる
  • ホスト環境を選ばない(デスクトップOSであればOK?)
  • npm依存を利用してフロントエンド技術でGUI構築ができる
  • イベントハンドラや処理をGoで記述することができ、JSコンテキストから呼べる

いろいろ試してきた結論

  • IMEがちゃんと使えるものという条件にすると途端に選択肢が半減する
  • なおかつクロスプラットフォーム対応とすると激減する
  • 重厚な作りのものはどうしても更新がゆっくりになる
  • 独自実装のものはどうしても期待しない挙動に悩まされる
  • 独自かつ重厚なものは事前にノウハウを積み上げないとなかなか思ったように作れない
  • 簡素な作りのものはしっかり更新されるけど大抵ウィジェットや機能が不足する
  • ブラウザエンジンやOSネイティブのWebViewに乗っかるのが結局無難な落とし所
  • かといってElectronアプリは起動が重いのが難点
  • というわけでlorcaかwailsがおすすめ
  • ノウハウの積み上げを覚悟してtherecipe/qtを頑張るのも一手
  • Qtでモダンに書く、HTMLで書くどちらも「Goで書ける」わけではないのでそれならFlutterという選択肢もある
  • ただ、HTML+Svelteの方がWebアプリにも応用できるノウハウなのでおすすめ

追記

https://github.com/nobonobo/dr2telemetory

ちょっとしたツールをWailsV2+SvelteKitで作ってみた。ウインドウのグラス透明化やボーダーレスウインドウを移動できるとか意外と痒い所に手が届いた。
Goの実装をフロントから呼ぶ、フロントの実装をGoから呼ぶあたりがまだよくわかってなくて、カスタムイベントで全部回した。

追記2

https://zenn.dev/nobonobo/articles/c775251fe739bc

Discussion

NoboNoboNoboNobo

SvelteKit側が自前コマンドでの開発がviteコマンドに移行したっぽいのでテンプレートも修正したー。