本文へスキップ

拡張の作成

拡張は、通常の Go コンポーネント、状態を保持するネイティブコンポーネント、非同期ネイティブサービスを提供できます。Editor、Markdown、Terminal は任意のネイティブコンポーネント、Updater は任意のサービスです。すべて同じ公開登録インターフェースを使い、第三者の拡張も quickgui-host の変更を必要としません。

#拡張の種類を選ぶ

追加するもの配布するもの
コンポーネントまたはアプリケーションロジックGo モジュール
ネイティブサービスまたはオプションのバックエンドGo パッケージ、マニフェスト、ネイティブライブラリ
状態を保持するネイティブ UIコンポーネント SDK ライブラリと Go バインディング

利用者はあなたの Go パッケージをインポートします。CLI はマニフェストを見つけ、対応するネイティブ成果物を同梱します。

#拡張プロジェクトを作成する

quickgui init-extension my-components --type go
quickgui init-extension my-service --type zig
quickgui init-extension my-service --type rust

go が既定で、再利用可能なコンポーネントの雛形を作ります。zig と rust は、型付き Go ラッパー、マニフェスト、npm 成果物パッケージ付きのネイティブサービスの雛形を作ります。どのテンプレートにも cmd/demo と quickgui.toml が含まれます:

cd my-service
bun run dev

Go モジュールには --module github.com/acme/my-service、成果物パッケージには --npm-package @acme/extension-my-service を使います。Zig テンプレートには Zig 0.16.x が必要です。このチェックアウトでは、CLI が公開されるまで bun packages/cli/src/cli.ts init-extension を実行します。

#コンポーネントの共有

独自モジュールからコンポーネント関数をエクスポートします。このパッケージにネイティブマニフェストは不要です。

package notice

import "github.com/egoist/quickgui/go/ui"

func Notice(message string, styles ...ui.StyleBuilder) *ui.Element {
	return ui.Text(
		message,
	).
		Padding(12).
		BackgroundColor("#eff6ff").
		TextColor("#1e40af").
		BorderRadius(8).
		Style(ui.Style().
			Merge(styles...))
}

Go のバージョンタグで公開します。利用者は go get で追加します。terminal や updater をインポートすると、それらのネイティブパッケージがアプリに入ります。オプションの連携は別パッケージに分けてください。

#ネイティブ拡張の構成

extensions/ の各組み込み拡張は Go モジュールと npm パッケージを兼ねます。Rust は src/、TypeScript バインディングは js/ に置きます。独自の複合パッケージにも同じ構成を使えます:

Cargo.toml
go.mod
badge.go
package.json
quickgui.extension.json
src/
  lib.rs
js/
  index.ts
lib/
  darwin-arm64/libquickgui_acme_badge.dylib

C、C++、Rust など C ABI を公開できる言語で実装できます。Rust のコンポーネントライブラリは、レンダラーや quickgui-host ではなく quickgui-extension-sdk に依存します。Component と ComponentFactory を実装すると、ComponentRuntime がライフサイクルの関数表を提供します。名前、コンポーネント名、プロパティ、リリースバージョンは拡張が定義します。

#依存関係の宣言

このマニフェストはインポートする Go パッケージのソースの隣に置きます:

{
  "schema": 1,
  "name": "acme-echo",
  "abi": 1,
  "package": "@acme/extension-echo",
  "version": "1.0.0",
  "library": "quickgui_acme_echo"
}

名前は小文字で始まり、小文字・数字・ハイフンを最大 64 文字まで含めます。host は予約されています。library フィールドは quickgui_ に、ハイフンをアンダースコアに置き換えた名前を足したものです。

Go パッケージはその要求を記録し、型付きの呼び出しを公開します:

package echo

import (
	"encoding/json"

	"github.com/egoist/quickgui/go/host"
	"github.com/egoist/quickgui/go/native"
)

func init() {
	host.RequireExtension("acme-echo", "1.0.0")
}

func Send(message string, done func(string, error)) {
	native.InvokeExtension("acme-echo", "echo", message, func(raw string, err error) {
		var reply string
		if err == nil {
			err = json.Unmarshal([]byte(raw), &reply)
		}
		done(reply, err)
	})
}

操作は native.Run の準備ができたあとに呼び出してください。永続的なサービスでは native.OpenExtension(name, options, changed, ready) がセッションを返します。所有者が破棄されたら、UI ゴルーチン上で閉じてください。

CLI は go list -deps でマニフェストを見つけます。マニフェストと RequireExtension の両方が必要です。一方は同梱を、もう一方は読み込みを制御します。

#ネイティブインターフェースを実装する

名前、独立したバージョン、ABI バージョン 1、汎用関数表を持つディスクリプタを、C リンケージの quickgui_extension_v1 から返します。コンポーネント表は create、update、render、event、destroy、サービス表は invoke、shutdown です。

コンポーネントは不透明なインスタンスを保持し、SDK のプリミティブツリーを返します。安定したノードキーでフォーカス、選択、スクロールを保持し、仮想リストは表示行とオーバースキャンだけを要求します。入力は拡張に戻り、出力イベントは Go バインディングへ渡ります。インスタンスへの呼び出しはネイティブ UI スレッド上で直列に実行されます。重い処理はワーカーに移し、データが変わったときだけそのインスタンスを起こします。同じプロパティには変更なしを返し、破棄時に状態と wake ハンドルを解放してください。

Go ラッパーから任意の登録済みコンポーネントをマウントできます:

package badge

import (
	"github.com/egoist/quickgui/go/host"
	"github.com/egoist/quickgui/go/ui"
)

func init() { host.RequireExtension("acme-badge", "1.0.0") }

func View(label string) *ui.Element {
	return ui.ExtensionComponent("acme-badge", "badge", map[string]any{
		"label": label,
	}, ui.Props{})
}

独立してコンパイルされる C カウンター に完全なディスクリプタとライフサイクル実装があります。コンポーネントフレームは 16 MiB、フロントエンドのプロパティ JSON は 12 MiB が上限です。

サービスでは、借用したリクエストを保持する前にコピーし、すぐに戻って後から応答します。

リクエスト拡張が行うこと
echo のようなワンショットのメソッド1 回応答する
startセッションを開き、そのイベントシンクを保持する
send のようなコマンドそのセッションを見つけ、このコマンドに応答する
stopそのセッションをキャンセルする
shutdownすべてのセッションをキャンセルし、ブロックしない

リクエストと応答のペイロードはそれぞれ 64 KiB までです。C の echo 実装 は、完結したワンショットの例です。

#自動登録

アプリは起動時に選ばれた各ライブラリを読み込み、名前、バージョン、ABI を確認します。同じ名前のバージョンが衝突すると失敗します。ライブラリイメージはプロセスの生存期間中ロードされたままです。shutdown 関数は残っているセッションをキャンセルしなければなりません。

#成果物のビルドとパッケージ化

サポートする各ホスト向けにネイティブライブラリをビルドし、lib/<platform>-<arch>/ に配置します。そのフォルダーを、マニフェストと同じ version の npm パッケージとして公開します。

{
  "name": "@acme/extension-echo",
  "version": "1.0.0",
  "files": ["js", "lib", "quickgui.extension.json", "README.md"],
  "exports": { ".": "./js/index.ts", "./package.json": "./package.json" }
}

TypeScript バインディングを含む npm パッケージは js/index.ts を公開します。@quickgui/* ランタイムと Solid は peer dependencies にし、開発用コピーは devDependencies に置き、実行時の dependencies には含めません。README はビルド手順だけにし、利用ガイドはウェブサイトに置きます。

ヘルパーファイルはライブラリの隣に置きます。ベース名はマニフェストの resources オブジェクトに、darwin、linux、windows をキーにして宣言します。

#テストと配布

利用者はモジュールを go get し、quickgui dev を実行します。CLI は対応する npm パッケージを取得します。開発中は QUICKGUI_EXTENSION_DIR をローカルの成果物ディレクトリに設定することもできます。

インポートのないアプリがその拡張を同梱しないこと、インポートを追加すると含まれること、セッションを閉じるとイベントが止まることを確認してください。エンドユーザーが受け取るのはパッケージ済みアプリです。実行に Bun やネイティブのツールチェーンは不要です。