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 も書き込まれます。
| テンプレート | ファイル |
|---|---|
go | nsgo.yaml、app.go、app_test.go |
tsx | nsgo.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) |
| ピクセル単位/無限フィールド/3D | core.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.AlignCenter | 2つの列挙型があります: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 でテストし、そのエラーを報告してください |
スクロールしない、バーが表示されない Scroll | Scroll のデフォルトは 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 プロジェクトの成果に基づいています。