---
title: "Structure du projet — Docs zenit Zig UI"
description: "Placez le point d’entrée du build, l’UI des fonctionnalités, les styles et les ressources là où ils pourront grandir — et sachez à qui appartient chaque bloc…"
url: https://zenit.z.express/fr/docs/guide/project-structure
language: fr
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_ja: https://zenit.z.express/ja/docs/guide/project-structure.md
alternate_ko: https://zenit.z.express/ko/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
---

# Structure du projet

Placez le point d’entrée du build, l’UI des fonctionnalités, les styles et les ressources là où ils pourront grandir — et sachez à qui appartient chaque bloc de mémoire.

## Structure suggérée

zenit n’impose pas d’arborescence. Gardez les fichiers de build stables et placez côte à côte l’arbre, l’état et les styles de chaque fonctionnalité, pour que modifier une fonctionnalité ne vous oblige pas à parcourir tout le dépôt.

`myapp/`

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

## Rôle des fichiers

| Fichier | Contient | À éviter |
| --- | --- | --- |
| `main.zig` | Allocator, initialisation de l’App, montage racine | Accumuler l’UI des pages |
| `view.zig` | L’arbre de nœuds et le câblage des événements | Couleurs brutes, transformations de données lourdes |
| `state.zig` | L’état métier et ses méthodes | Des pointeurs durables vers des nœuds susceptibles d’être supprimés ou reconstruits |
| `styles.zig` | Fonctions de style nommées pilotées par Token, `fn (*const ui.ThemeTokens) ui.BoxStyle` | Faire des IO ou modifier l’état |

Les fonctions de style sont pures ; des constructeurs comme `ui.boxStyled` / `ui.vstackStyled` les évaluent au montage et les rejouent avec les nouveaux tokens quand le thème change — les styles littéraux en ligne n’en bénéficient pas.

`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, .{});
```

## Frontière d’import

La surface publique comporte deux niveaux : les types et constructeurs courants sont dans `ui.X`, les fonctionnalités avancées sont regroupées sous `ui.<group>.X` (`ui.widgets`, `ui.fx`, `ui.events`, `ui.theme`, …). Tout ce qui sort de ces deux niveaux est interne.

```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;
```

Le système de build l’impose : `zenit.attach` ne fournit à l’application que les modules `ui` et `zenit_app`, si bien que les modules internes comme `render` et `gpu` ne peuvent tout simplement pas être importés (voir le schéma de la [Vue d’ensemble](https://zenit.z.express/fr/docs#architecture)).

> WARNING
> 
> **L’API est antérieure à la 1.0.** zenit est public et suit semver depuis la 0.1.0, mais avant la 1.0 une version minor peut encore introduire des ruptures (chaque changement est consigné dans docs/MIGRATION.md). Épinglez une version ou une révision précise plutôt que de suivre la tête d’une branche.

## Propriété et durées de vie

Deux propriétaires, deux durées de vie. `Cx` est au niveau de la fenêtre : il possède l’arbre de nœuds et chaque struct allouée par `cx.bindState` jusqu’à `Cx.deinit`. `Scope` possède les ressources réactives — Signals, Memos, Effects, callbacks `onCleanup` et ressources enregistrées — et les libère lors de `dispose()` ; les scopes enfants partent avec leur parent.

OWNERSHIP

Disposer un Scope de page ne libère pas les structs que vous avez créées avec `bindState` sur cette page — elles vivent jusqu’à la fermeture de la fenêtre. `Cx.deinit` exécute : dispose du Scope racine → libération des nœuds → libération de l’état.

### État lié : déclarez un pub deinit

Si la struct déclare un `deinit(*T)` **pub**, le framework l’appelle automatiquement avant de libérer la struct lors de `Cx.deinit`. Si le nettoyage a besoin d’un allocator, stockez-le dans un champ.

`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
> 
> **Ne nettoyez pas deux fois.** Déclarer un `deinit` pub sur une struct `bindState` *et* enregistrer `scope.onCleanup(…, T.deinit)` exécute deinit deux fois — une fois au dispose du scope, une fois à `Cx.deinit`. Un `deinit` non pub n’est pas détecté et n’est jamais appelé automatiquement.

### Ressources à durée de vie de page : onCleanup

N’utilisez `scope.onCleanup` que dans deux cas : le nettoyage n’est pas pub, ou la ressource doit suivre la durée de vie d’un Scope (une page, par exemple) plutôt que celle de la fenêtre. Allouez alors ces objets vous-même au lieu de passer par `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 }, .{});
}
```

## Liste de contrôle

-   **Gardez main.zig court.** Allocator, `App.init` et `runWith` — rien d’autre.
    
-   **N’importez que ui et zenit\_app.** Si ce dont vous avez besoin ne figure pas dans les deux niveaux publics, ouvrez une issue plutôt que d’aller fouiller dans les internes.
    
-   **Durée de vie de fenêtre → bindState, de page → Scope.** Donnez au premier un `deinit` pub, au second un `onCleanup` — jamais les deux.
    
-   **Écrivez les styles comme des fonctions nommées.** Placez-les dans styles.zig et utilisez-les via les constructeurs `*Styled` pour que les changements de thème fonctionnent d’office.
