WebUI とは
Cで書かれたクライアントアプリケーション構築用のライブラリです(バックエンドとのブリッジ部分は TypeScriptで書かれています)。
フロントエンドには成熟したWeb技術を使い、バックエンドは C や Go, Rust や Python などを使って開発できます(WebUIの各種言語向けラッパーが提供されています)。
フロントエンドにはElectron 系のように WebView/BrowserView を組み込みで使うというものとは異なり、単にインストール済みのWebブラウザを使うのが大きな特徴で、そのためアプリケーション自体のフットプリントが小さくなります。
簡単な実例
Go 言語向けのラッパを例に、動作を確認してみます。
ここでは Go-WebUI の example アプリケーションを動かすことにしましょう。
以下でリポジトリをクローンします。
$ git clone https://github.com/webui-dev/go-webui.git
リポジトリにはセットアップスクリプトが用意されているので、これを実行します(mac または linux の場合は setup.sh
、Windows Powershell の場合は setup.ps1
が用意されています)。
オプションは以下のものがあります。
$ ./setup.sh -h Usage: setup.sh [flags] Flags: -o, --output: Specify the output directory --nightly: Download the lastest nightly release --local: Save the output into the current directory -h, --help: Display this help message
このスクリプトは、webui のプラットフォーム別のリリースアーカイブをダウンロードして解凍・配備するだけのものです。
--local
を指定した場合は、カレントのv2
ディレクトリに、そうでない場合は GOPATH
配下のモジュール("$go_path/pkg/mod/$module@$version"
)として webui
が配備されます。
以下を実行します。
$ cd go-webui ./setup.sh --local
Windows の場合は以下を実行します。
> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process > .\setup.ps1 --local
v2/webui
配下に WebUI の実行バイナリが配備されます。
サンプルを v2
にコピーして実行してみましょう。
$ cp -r examples v2/ $ cd v2/examples $ go run minimal.go
実行すると、ブラウザがキオスクモードで起動し、以下のように表示されます。
minimal.go
は以下のようなコードになります。
package main import "github.com/webui-dev/go-webui/v2" func main() { w := webui.NewWindow() w.Show("<html>Hello World</html>") webui.Wait() }
webui.NewWindow()
で作成した WebUI ウインドウオブジェクトに対して Show
で HTML を表示する簡単なものです。
Go からフロントエンドの呼び出し
サンプルが用意されているので実行してみましょう。
$ go run call_js_from_go.go
以下のウインドウが表示され、ボタン押下でカウントアップします。
call_js_from_go.go
は以下のコードとなっています。
package main import ( "fmt" "strconv" ui "github.com/webui-dev/go-webui/v2" ) // UI HTML const doc = `<!DOCTYPE html> <html> <head> <title>Call JavaScript from Go Example</title> <script src="webui.js"></script> <style>...略</style> </head> <body> <h1>WebUI - Call JavaScript from Go</h1> <br> <button id="MyButton1">Count <span id="count">0<span></button> <br> <button id="MyButton2">Exit</button> <script> let count = document.getElementById("count").innerHTML; function SetCount(number) { document.getElementById("count").innerHTML = number; count = number; } </script> </body> </html>` func myCountFunc(e ui.Event) any { count, _ := e.Window.Script("return count;", ui.ScriptOptions{}) i, _ := strconv.Atoi(count) e.Window.Run(fmt.Sprintf("SetCount(%v);", i+1)) return nil } func myExitFunc(e ui.Event) any { ui.Exit() return nil } func main() { w := ui.NewWindow() w.Bind("MyButton1", myCountFunc) w.Bind("MyButton2", myExitFunc) w.Show(doc) ui.Wait() }
HTML <button id="MyButton1">...</button>
のアクションに応答するには、以下の様に Go 関数に紐づけます。
w.Bind("MyButton1", myCountFunc)
myCountFunc
は以下のようになっています。
func myCountFunc(e ui.Event) any { count, _ := e.Window.Script("return count;", ui.ScriptOptions{}) i, _ := strconv.Atoi(count) e.Window.Run(fmt.Sprintf("SetCount(%v);", i+1)) return nil }
Window.Script
では、JavaScriptを任意のウィンドウ上で実行します。これにより、値を読み込んだり、ビューを更新したりすることができます。
e.Window.Script("return count;", ui.ScriptOptions{})
ではカウントの値を取得しています。
Window.Run
では、レスポンスを待たずにJavaScriptを素早く実行することができます。ここでは JavaScript で定義した SetCount
関数を呼び出してカウントを設定しています。
JavaScript から Go 関数の呼び出し
フロントエンド側で webui.handleStr()
を以下のように定義することで、フロントエンド側からGo関数をコールすることができます。
<!DOCTYPE html> <html> <head> <script src="webui.js"></script> </head> <body> <button onclick="webui.handleStr('Hello', 'World');">Call handle_str()</button> </body> </html>
バックエンド側では以下のようにGo関数と紐づけることができます。
// JavaScript: `webui.handleStr('Hello', 'World');` func handleStr(e ui.Event) ui.Void { str1, err := ui.GetArg[string](e) if err != nil { fmt.Println(err) return nil } str2, _ := ui.GetArgAt[string](e, 1) fmt.Printf("handleStr 1: %s\n", str1) // Hello fmt.Printf("handleStr 2: %s\n", str2) // World return nil } func main() { // ... ui.Bind(w, "handleStr", handleStr) }
これらの例もサンプルがあるので以下により実行することができます。
$ go run call_go_from_js.go
以下のウインドウが表示され、JavaScript から Go 関数の呼び出しを確認できます。
その他にもテキストエディタの実装サンプルなどもあり、以下のように試すことができます。
$ cd text-editor $ go run main.go
結局 WebUI とはなにものなのか
WebUI のソースは以下のように非常にシンプルです。
├─bridge │ build.sh │ js2c.py │ utils.ts │ webui_bridge.h │ webui_bridge.ts │ ├─include │ webui.h │ webui.hpp │ └─src │ webui.c │ └─civetweb civetweb.c civetweb.h handle_form.inl match.inl md5.inl response.inl sha1.inl sort.inl
主に以下の3ソースで構成されます。
src/webui.c
:WebUI バックエンド実装include/webui.h
:WebUI APIbridge/webui_bridge.ts
: WebSocket でバックエンドと連携を行うフロントエンドライブラリ
webui.c
では、C (C/C++) 組み込み Web サーバー civetweb(Mongoose からの MITフォーク)を利用した、WebSocket サーバと、ブラウザウインドウの管理を行います。civetweb
ディレクトリには civetweb から必要なソースに絞ってコピーされたものが格納されています。
webui_bridge.ts
は WebSocket のクライアント実装で、サーバとのやり取りは、WebUI で定義するバイナリデータでやり取りを行います。
つまり、WebUI は単に、「WebSocket によるRPC ライブラリ+ブラウザウインドウ管理を提供する」といったところになります。
ローカルで起動したWebサーバで、キオスクモードのブラウザとやり取りするだけのものです。
そういえば昔、WebSocket でサーバ側で画像を生成してクライアントに送信し、クライアントではサーバから受信した画像表示だけを行う形でテトリス作ったのを思い出しました。