---
title: "プロジェクト構成 — zenit Zig UI ドキュメント"
description: "ビルドのエントリ、機能ごとの UI、スタイル、アセットを拡張しやすい場所に置き、各メモリの所有者を把握します。"
url: https://zenit.z.express/ja/docs/guide/project-structure
language: ja
alternate_en: https://zenit.z.express/docs/guide/project-structure.md
alternate_zh: https://zenit.z.express/zh/docs/guide/project-structure.md
alternate_es: https://zenit.z.express/es/docs/guide/project-structure.md
alternate_ko: https://zenit.z.express/ko/docs/guide/project-structure.md
alternate_fr: https://zenit.z.express/fr/docs/guide/project-structure.md
alternate_de: https://zenit.z.express/de/docs/guide/project-structure.md
project: zenit v0.1.0-alpha (Zig 0.15.2, macOS)
source: https://github.com/version-next/zenit
---

# プロジェクト構成

ビルドのエントリ、機能ごとの UI、スタイル、アセットを拡張しやすい場所に置き、各メモリの所有者を把握します。

## 推奨構成

zenit はディレクトリ構成を強制しません。ビルドファイルは安定させたまま、機能ごとのツリー、状態、スタイルを同じディレクトリに並べると、1 つの機能を変更するときにリポジトリ内を行き来せずに済みます。

`myapp/`

```
myapp/
├── build.zig
├── build.zig.zon
└── src/
    ├── main.zig
    ├── app_state.zig
    ├── assets.zig
    └── features/
        └── dashboard/
            ├── view.zig
            ├── state.zig
            └── styles.zig
```

## ファイルの役割

| ファイル | 担当 | 避けること |
| --- | --- | --- |
| `main.zig` | Allocator、App の初期化、ルートのマウント | 個別ページの UI を詰め込むこと |
| `view.zig` | ノードツリーとイベントの接続 | 生の色値、重いデータ変換 |
| `state.zig` | ビジネス状態とそのメソッド | 削除・再構築されうる Node へのポインターを長く保持すること |
| `styles.zig` | Token 駆動の名前付きスタイル関数 `fn (*const ui.ThemeTokens) ui.BoxStyle` | IO を行うことや状態を変更すること |

スタイル関数は純粋関数で、`ui.boxStyled` / `ui.vstackStyled` などのビルダーがマウント時に評価します。テーマが切り替わるとフレームワークが新しいトークンで再適用しますが、インラインのリテラルスタイルは対象外です。

`styles.zig + view.zig`

```zig
// features/dashboard/styles.zig
const ui = @import("ui");

pub fn card(t: *const ui.ThemeTokens) ui.BoxStyle {
    return .{
        .padding = ui.Padding.all(t.space._6),
        .gap = t.space._4,
        .background = t.color.bg_secondary,
        .corner_radius = t.radius.xl,
    };
}

// features/dashboard/view.zig
const S = @import("styles.zig");
const panel = try ui.vstackStyled(cx, S.card, .{});
```

## インポートの境界

公開 API は 2 層です。よく使う型とビルダーは `ui.X` に、高度な機能は関心ごとに `ui.<group>.X`（`ui.widgets`、`ui.fx`、`ui.events`、`ui.theme` など）にまとめられています。この 2 層以外はすべて内部実装です。

```zig
const ui = @import("ui");

// Tier 1: what most UI code needs
const Node = ui.Node;
const Signal = ui.Signal;

// Tier 2: grouped by concern
const Router = ui.fx.Router;
const Button = ui.widgets.Button;
const Event = ui.events.Event;
```

この境界はビルドシステムが保証します。`zenit.attach` はアプリに `ui` と `zenit_app` の 2 モジュールだけを追加するため、`render` や `gpu` などの内部モジュールはそもそもインポートできません（構成図は[概要](https://zenit.z.express/ja/docs#architecture)を参照）。

> WARNING
> 
> **API はまだ 1.0 前です。** zenit は 0.1.0 から公開され、セマンティックバージョニングに従っていますが、1.0 より前は minor バージョンでも破壊的変更がありえます（すべて docs/MIGRATION.md に記録）。ブランチの最新コミットを追いかけず、正確なバージョンまたは revision に固定してください。

## 所有権とライフタイム

所有者は 2 つ、寿命も 2 種類です。`Cx` はウィンドウ単位で、ノードツリーと `cx.bindState` が確保したすべての struct を `Cx.deinit` まで所有します。`Scope` はリアクティブなリソース（Signal、Memo、Effect、`onCleanup` コールバック、登録されたリソース）を所有し、`dispose()` で解放します。子 Scope は親と一緒に解放されます。

OWNERSHIP

ページの Scope を破棄しても、そのページで `bindState` した struct は解放されません。ウィンドウが閉じるまで生き続けます。`Cx.deinit` の順序は、ルート Scope の dispose → ノードの解放 → state の解放です。

### バインドした状態：pub deinit を宣言するだけ

struct が **pub** な `deinit(*T)` を宣言していれば、フレームワークは `Cx.deinit` で struct を解放する前に自動で呼び出します。クリーンアップに allocator が必要なら、フィールドとして保持してください。

`state.zig`

```zig
const Item = struct { title: []const u8 };

const Model = struct {
    allocator: std.mem.Allocator,
    items: std.ArrayList(Item) = .empty,

    // Must be pub: Cx.deinit detects it and calls it before freeing the struct.
    pub fn deinit(self: *Model) void {
        self.items.deinit(self.allocator);
    }
};

const model = try cx.bindState(Model, .{ .allocator = cx.allocator });
// Do NOT also call scope.onCleanup(Model, model, Model.deinit):
// that would run deinit twice.
```

> WARNING
> 
> **二重にクリーンアップしないでください。** `bindState` の struct に pub な `deinit` を宣言し、*さらに* `scope.onCleanup(…, T.deinit)` を登録すると、scope の dispose 時と `Cx.deinit` 時に deinit が 1 回ずつ、計 2 回実行されます。pub でない `deinit` は検出されず、自動では呼ばれません。

### ページ単位のリソース：onCleanup

`scope.onCleanup` が必要なのは 2 つの場合だけです。クリーンアップ関数が pub でない場合と、リソースをウィンドウではなく特定の Scope（たとえばページ）の寿命に合わせて解放する必要がある場合です。こうしたオブジェクトは `bindState` を使わずに自分で確保します。

`features/dashboard/view.zig`

```zig
const PageCache = struct {
    allocator: std.mem.Allocator,
    rows: std.ArrayList(Item) = .empty,

    fn release(self: *PageCache) void {
        self.rows.deinit(self.allocator);
        self.allocator.destroy(self);
    }
};

fn mountDashboard(cx: *ui.Cx, parent: *ui.Scope) !*ui.Node {
    const scope = try parent.childScope();

    // Page-lifetime resource: freed when this scope is disposed,
    // not when the window closes.
    const cache = try cx.allocator.create(PageCache);
    cache.* = .{ .allocator = cx.allocator };
    scope.onCleanup(PageCache, cache, PageCache.release) catch |err| {
        cx.allocator.destroy(cache);
        return err;
    };

    return ui.vstack(cx, .{ .gap = cx.tokens.space._4 }, .{});
}
```

## チェックリスト

-   **main.zig は短く保つ。**allocator、`App.init`、`runWith` だけにします。
    
-   **インポートは ui と zenit\_app だけ。**必要な機能が 2 層の公開 API にない場合は、内部に手を伸ばさず issue を立ててください。
    
-   **ウィンドウの寿命には bindState、ページの寿命には Scope。**前者には pub な `deinit`、後者には `onCleanup` を使い、両方を重ねないでください。
    
-   **スタイルは名前付き関数で書く。**styles.zig に置き、`*Styled` ビルダーで使えば、テーマ切り替えがそのまま反映されます。
