Communitygithub.com

NSGo

クロスプラットフォームなネイティブUIエンジン。Goでアプリロジック、リアクティブグラフ、スタイル、レイアウト、テキストを記述し、各プラットフォームのネイティブビューで描画します。

NSGo とは?

NSGoは、Go言語のバックエンドとネイティブUIを組み合わせたクロスプラットフォームアプリケーション開発フレームワークです。macOS、iOS、Windows、Linux、Android、Webをターゲットとしており、アプリのロジックやスタイル、レイアウトはGoで記述され、各プラットフォームのネイティブビューで描画されます。TSXで記述されたアプリケーションもgoja上で実行可能です。シグナルベースのリアクティブプログラミングモデルを採用しており、状態変更が自動的にUIに反映されます。アニメーション機能も充実しており、プリセットアニメーション、カスタムアニメーション、シェーダー駆動のモーション、3Dシーンなどをサポートしています。`go tool nsgo` というCLIツールを使用して、プロジェクトの作成、開発サーバーの起動、テスト、ビルドを行います。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/HycJack/nsgo-motion-lab/tree/HEAD

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

NSGo は何をしますか?

Go UI エンジン:アプリロジック、リアクティブグラフ、スタイル、レイアウト、テキストは Go で実行され、各プラットフォームは独自のネイティブビューで描画します。アプリは Go(または TSX をコンパイルした goja)で記述します。設計、名前、動作は NSH(Rust)から移植されています。

鉄則:API を読み、推測するな

NSGo はプレリリース版であり、安定した API はなく、ウェブ上の公開ドキュメントもほとんどありません。そのため、記憶から識別子を思い出すとほぼ間違っています。Go ではフィールド名が一つ間違っているだけでもコンパイルに失敗します。アプリを作成する前に、モジュール自体から正しい情報を取得してください:

go doc github.com/oneo-me/nsgo/core      # または:go doc -all ./core
go doc github.com/oneo-me/nsgo/base

リポジトリのチェックアウトでは、ARCHITECTURE.md、docs/architecture/*.md(主題別ドキュメント25件)、および examples/ が信頼できる情報源です。examples/gallery はすべての要素、コンポーネント、サービスを網羅しています。モジュールもリポジトリも利用できない場合は、その旨を伝えてください。ユーザーの実際のリクエストに対して、別のフレームワーク(Fyne、Gio、Wails)を代用しないでください。

セットアップ

Go 1.26.0 以降 が必要です(go.mod に go 1.26.0 と記載)。

OSネイティブターゲット備考
macOS✅完全なツールチェーン:Go + Xcode ですべてのターゲットをビルド/署名
Windows✅ネイティブ + Web のビルドと実行
Linux✅GTK 4.14+、Pango、Cairo が必要。Web ビュー/動画には WebKitGTK 6.0 と GStreamer が必要

モジュールとそのツールをアプリに一度追加します。その後、go tool nsgo をインストールせずに実行できます:

go get -tool github.com/oneo-me/nsgo/cmd/nsgo

NSGo は タグ付きバージョンを公開していない ため、モジュールは擬似バージョン(v0.0.0-2026…-46c7d9c)に解決されます。一部のリージョンミラーはタグなしモジュールに対して404を返します。go get が見つけられない場合は、プロキシを固定してください:

GOPROXY=https://proxy.golang.org,direct GOSUMDB=off go get -tool github.com/oneo-me/nsgo/cmd/nsgo

GOSUMDB=off は、sum.golang.org に到達できない場合にのみ必要です。代わりにローカルチェックアウトから作業する場合は、それを指定して tidy を実行します:

go mod edit -replace github.com/oneo-me/nsgo=/path/to/nsgo && go mod tidy

プロジェクトの作成

mkdir counter && cd counter
go mod init counter
go get -tool github.com/oneo-me/nsgo/cmd/nsgo   # ライブラリとツール
go tool nsgo init -name counter .              # nsgo.yaml、app.go、app_test.go を作成
go mod tidy
go tool nsgo dev .                             # 実行、保存のたびに再ビルド
go tool nsgo test .                            # ヘッドレス:2 passed

init には名前が必要です。 既存のディレクトリで単に go tool nsgo init を実行すると、"." is not an app name で失敗します。これはデフォルトの名前がディレクトリ名から取得され、. の場合は . になるためです。-name を渡すか、作成するディレクトリを渡します:

コマンド効果
go tool nsgo init -name Counter .現在のディレクトリに書き込み、アプリ名は Counter
go tool nsgo init Counter./Counter/ ディレクトリを作成し、アプリを配置
go tool nsgo init -template tsx Counter./Counter/ を TSX アプリとして作成

init は自身の次のステップ(next: go tool nsgo dev Counter)を出力します。記憶よりもこちらの表示を信頼してください。

-template は go(デフォルト)、tsx、mixed のいずれかです。アプリ名には英数字、スペース、-、_ を使用できます。バンドル ID は dev.nsgo.<名前> になります。-module を渡すと、go.mod も書き込まれます。

テンプレートファイル
gonsgo.yaml、app.go、app_test.go
tsxnsgo.yaml、main.go、App.tsx、app.test.tsx、tsconfig.json、.gitignore
mixed上記に加え、App.tsx が独自の Go コードと native.go をインポート

nsgo.yaml はアプリとそのターゲットを指定します:

name: Hello
id: dev.oneo.nsgo.hello
version: 0.1.0
dev:
  target: native        # native | web | ios | ios_simulator | android
  hot_reload: true
  fps: false
build:
  output: dist
  targets: [macos_arm64, windows_x64, ios_simulator, android_arm64, web]

その他に有効なもの:build_number、appearance、theme、i18n、icon.{apple,png}、dev.icon、android.permissions、sign.{profile,keystore}、web.host。その他に、resources/ には出荷ファイル(asset: として読み込み)、assets/ にはプロジェクト専用ファイル(アイコン)、locales/ には ICU MessageFormat 形式の en.yaml、de.yaml… を格納します。

得られるアプリ(-template go)

import (
	"fmt"

	"github.com/oneo-me/nsgo"
	"github.com/oneo-me/nsgo/base"
	"github.com/oneo-me/nsgo/core"
)

func Root(cx *core.Scope) core.Node {
	count := core.NewSignal(cx, 0)
	return core.View{Style: core.Style{FlexGrow: 1, Padding: core.All(24), Gap: 16}, Children: []core.Node{
		core.Text{ContentFunc: func() string { return fmt.Sprintf("Pressed %d times", count.Get()) }},
		base.Button{
			Label:   "Press me",
			OnPress: func(core.PressEvent) { count.Update(func(n int) int { return n + 1 }) },
			Style:   core.Style{Background: core.RGB(0x0969da), CornerRadius: 8, Padding: core.Symmetric(10, 16)},
			Children: []core.Node{core.Text{Content: "Press me", Style: core.Style{Color: core.RGB(0xffffff)}}},
		},
	}}
}

func main() { nsgo.Run(Root) }

nsgo.Run は、ルートビュー(func(cx *core.Scope) core.Node)またはアプリ関数(func(app *core.Scope))(自身のウィンドウを desktop 経由で開く)のいずれかを取ります。オプション:WindowSize(w, h)、Appearance(...)、HideDock()、FPS(bool)、WindowMaterial(...)、Fonts(paths...)、Component(name, factory)。

メンタルモデル

  • シグナル。 core.NewSignal[T comparable](cx, v) → .Get()、.Set(v)、.Update(fn)。NewMemo、NewEffect。グラフはエンジンのゴルーチンに属し、ロックは行われません。他のゴルーチンは Scope.Post でグラフに入ります。
  • 要素は構造体であり、ゼロ値がデフォルトです(http.Server と同様)。変更したいものだけを設定します。
  • すべてのリアクティブフィールドには双子がいます: Content + ContentFunc func() string、Style + StyleFunc、Class + ClassFunc。XFunc は X よりも優先されます。両方を設定するとマウント時にパニックになります。 静的フォームを持たないフィールドは関数のみを受け付けます(Disabled func() bool、Selected、Expanded)。
  • コンポーネント は Render(cx *core.Scope) core.Node を持つ任意の型です。フィールドは props です。core.Func はプレーンな関数を適応させます。
  • リージョン — Show、Each、Dynamic、Catch — は自身のスパンだけを再構築します。Each はジェネリック関数であり、メソッドではありません。
  • スタイリング: 型付けされた core.Style 値、または Class 内の Tailwind クラス("flex-row gap-4 p-4 bg-white dark:bg-zinc-900")。base のコンポーネントは意図的にスタイルが設定されていません。動作、状態、アクセシビリティのみを提供します。

モーション

2つのクロック。コミット = Go がスパンを再レンダリングします(シグナル、Show/Each/Dynamic)。フレーム = プラットフォームのアニメーターが AnimatedStyle フィールドとシェーダーユニフォームを、コミットなしでステップ実行します。すべてのモーションはフレームクロックに属します。レンダリング中に読み取られた値はサブツリーを再レンダリングし、その中のすべてのアニメーションを再起動します。

ニーズ使用法
状態変更(ホバー、プレス、選択、リサイズ)Style.Transition — 持続時間、core.EaseOut、および Properties ビットマスク。グラフは不要で、モーション軽減設定ではスキップされます
標準的な登場/退場/注意/ループcore.Motion + core.MotionPreset(38種類あり、すべてモーション軽減設定を尊重)
カスタム振付、オーバーラップ、ホールド、スワップAnimatedStyle + AnimatedNode グラフ(Timing、Spring、Interpolate、Sequence、Stagger、Loop)
ピクセル単位/無限フィールド/3Dcore.Shader ユニフォームをノードとして — コミットはゼロ
手続き型描画core.Canvas Draw(変更ごとに再レコード)または d.Bind(プラットフォームが再生、再レコードなし)
spec := core.FadeInUp.Spec()             // または独自の core.MotionSpec
m := core.NewMotion(cx, &spec)           // nil = 静止時に構築、後で Play
return core.View{Style: core.Style{Animated: m.Style()}, Children: []core.Node{card()}}

API よりも価値のある5つのルール:(1) レンダリング時にシグナルやアニメーション値を読み取ってはなりません。クロージャまたは AnimatedStyle のみを使用します;(2) 再構築ではなく再スタイルします;(3) 有限のアニメーションは最後の値を 保持 するため、終了時のフレームが読み取り可能なフレームである必要があります(開始に戻るループは、落ち着くと空白に見えます);(4) アニメーション要素には測定された親が必要です。すべて絶対配置の View は 0×0 と測定され、すべてをクリップします;(5) AnimatedStyle フィールドは AnimatedNode であり、数値ではありません(core.Constant(1)、1 ではない)。また、Width/Height/Perspective はありません。

アニメーションを作成する前に references/animation.md をお読みください。プリセットテーブル、機能したイージング/持続時間の数値、振付レシピ、シェーダーとキャンバスのモーション、3D(レイマーチング)、テスト、およびモーションバグの症状→原因テーブルが含まれています。

開発ループ、ビルド、テスト

タスクコマンド
実行、保存時に再ビルドgo tool nsgo dev <dir>
ブラウザで実行go tool nsgo dev -target web <dir>
シミュレータ/デバイスで実行go tool nsgo dev -target ios_simulator|android|ios <dir>
すべてのコミットをトレース... -trace
リリースパッケージgo tool nsgo build <dir>(-target all、-output DIR、-run、-device D)
ホストアプリに埋め込み... build -embed → XCFramework(iOS)/ AAR(Android)/ nsgo.js(Web)。デスクトップは nsgo.NewView を使用
テストgo tool nsgo test <dir>(-run REGEXP、-junit FILE、-target macos|windows|linux|web|ios_simulator|android、-fps)
TSX の型チェックgo tool nsgo check <dir>
メッセージカタログの再生成go tool nsgo i18n <dir>

フラグは、それらが指定する nsgo.yaml の値を上書きします。build.targets と -target は all、または windows_x64、macos_arm64、linux_x64、ios、web などの名前を受け入れます。build は -output(デフォルトは dist)の下に、ターゲットごとに <name>-<version>-<target>/ という名前のディレクトリを一つ書き込みます。デスクトップテストターゲットは、その OS 上でのみ実行されます(Windows では -target windows、Mac では -target macos)。web、ios_simulator、android は Mac から実行されます。

テストはユーザーのように動作します。nsgotest はウィンドウなしでヘッドレスで実行します。-target は、実際のウィンドウでネイティブ入力とスクリーンショットを使用して同じテストを実行します(app.Screenshot("name"))。

func TestMain(m *testing.M) { nsgotest.Main(m) }   // プラットフォームにメインスレッドを与える

func TestPress(t *testing.T) {
	app := nsgotest.Start(t, Root)
	app.Press("text:Press me")
	app.Find("text:Pressed 1 times")
}

セレクタは Playwright と同じです:id:、label:、text:(部分文字列。text:"Save" またはベア文字列 = テキスト全体)、role:<role> <name>、view:<n>、/regexp/flags、および >>(子孫へ)。ステップ:Press、PressDown/PressUp、LongPress、Key、KeyPress、Type、Focus、Hover、Scroll、ScrollTo、Drag、Touch、Back、Resize、Settle、Advance、WaitFor、WaitForGone、Exists、Query、Texts、Dump。完全なリスト:go doc github.com/oneo-me/nsgo/nsgotest。

TSX アプリも同じ形状です:App.tsx は "nsgo" から mount, signal, Text, View をインポートし(コンポーネントは "nsgo/base" から)、mount(App) で終わります。main.go はソースを埋め込み、nsgo.Run(compiler.App(src, "App.tsx")) を呼び出します。*.test.tsx ファイルは Go テストとして実行されます。references/api.md を参照してください。

よくある間違い

間違い現実
フィールド名を推測するNSGo はプレリリース版であり、公開ドキュメントがほとんどないため、記憶から思い出した識別子は間違っています。最初に go doc を実行してください。名前が一つ間違っているとコンパイルエラーになります
Content と ContentFunc の両方を設定するマウント時にパニックになります。どちらか一方を選んでください
NewSignal(cx, []string{...})T は comparable でなければなりません。独自の equal 関数を持つ NewSignalFunc を使用してください
スコープ外で NewSignal を使用するシグナルには cx が必要です。グラフはゴルーチンローカルであるため、cx が唯一のハンドルです。別のゴルーチンからは Scope.Post を使用してください
Button.Label がレンダリングされると期待するLabel はアクセシブルな名前です(role:/label: セレクタがマッチするもの)。表示されるテキストは Children から来ます
internal/... をインポートするアプリがインポートできるのは nsgo、core、base、mobile、desktop、i18n、icons、tsx、nsgotest のみです
Windows または Linux で go build ./... を実行する失敗します:internal/platform/apple/edge.go に //go:build darwin タグがなく、darwin 専用の purego/objc をインポートしているためです。自身のパッケージ(go build .)をビルドするか、チェックアウト内でタグを追加してください
JustifyContent: core.AlignCenter2つの列挙型があります:AlignItems は core.AlignStart/Center/End/Stretch/Baseline を取り、JustifyContent は core.JustifyStart/Center/End/SpaceBetween/... を取ります。混同するとコンパイルエラーになります
core.Shader{OnMouseMove: ...}シェーダーには OnPress、OnLayout、Style があります。ポインタ移動ハンドラーはありません。OnMouseMove func(Point) / OnHoverOut func() は、その周囲の core.View に配置してください
MustCompileShader だけを信頼するKage は実行時にコンパイルされるため、悪いシェーダーはビルド時ではなく起動時にパニックになります。各ソースを core.CompileShader でテストし、そのエラーを報告してください
スクロールしない、バーが表示されない ScrollScroll のデフォルトは FlexShrink: 1 ですが、View の親はそうではありません。FlexGrow: 1 のみを持つカラムはコンテンツに合わせて成長し、スクロールビューはその高さを継承し、オーバーフローは発生しません。カラムに FlexGrow: 1, FlexShrink: 1(React Native の flex: 1)を指定してください。確認方法:スクロールのフレームはウィンドウより短くなければなりません
永続的なスクロールバーを探すインジケータはオーバーレイです。ネイティブは、スクロール中にフェードインし、700ms 保持し、その後 300ms でフェードアウトする丸いサムを描画します。HideIndicator で削除できます。Web では、ブラウザの独自のバーです(overflow-y: auto、scrollbar-width: auto)
Left のみで固定されたオーバーレイ水平方向の端が一つだけの絶対配置ビューは、コンテンツの幅ではなく、包含ブロックの 残りの 幅を取ります。26pt の円形アンカー内で Left: 34 にあるツールチップは、残りがなくなり、1行に1文字に折り返されます。明示的な Width を指定してください
補間色内のアルファcore.Interpolate で Colors: {RGBA(0xffffff, 0.12), ...} を指定した AnimatedStyle.Background は、12% ではなく、ほぼ白のベタ塗りとして描画されます。補間フィルには不透明な色を使用し、アルファは Style.Background 用に保持してください
MotionPreset.Name() をラベルとして使用するcamelCase(fadeIn、zoomIn、breathe)を返します。Go の定数名ではありません
アニメーションが「開始位置にジャンプバックする」レンダリング中に読み取られたシグナルまたはアニメーション値、または Dynamic のレンダリング内部で構築された NewAnimatedValue が原因です。スパンが再構築され、値が再開します。クロージャまたは AnimatedStyle 内でのみ読み取ってください

一般的なアニメーションの問題のトラブルシューティング

症状考えられる原因修正
アニメーションが完了しない/フリーズするアニメーション可能なプロパティが AnimatedStyle にない、または親が Animated スタイルを適用していないプロパティが Animated フィールドにあり、親ビューが m.Style() を使用していることを確認
アニメーションが一瞬で終わる持続時間がゼロ、またはアニメーションの開始/終了値が同じspec.Duration を確認、または値を異なるものにする
AnimatedStyle が効かないAnimatedStyle が Style に割り当てられていない?Style: core.Style{Animated: m.Style()} のように割り当てる
シェーダーがコンパイルされないシェーダーコードに構文エラーがある、または Kage の制限に違反しているまず core.CompileShader でテストする

ライセンスと帰属

NSGo は MIT ライセンス の下で公開されています。

このスキルの説明は、NSH プロジェクトの成果に基づいています。

関連スキル