diff --git a/Cargo.toml b/Cargo.toml index 5822712..47a476e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,15 @@ console_error_panic_hook = { version = "0.1", optional = true } # Pont entre Rust/WASM et JavaScript : permet d'appeler du JS depuis Rust wasm-bindgen = { version = "0.2", optional = true } +# Configuration Leptos lue par get_configuration(Some("Cargo.toml")) +# Définit les chemins des fichiers compilés et l'adresse du serveur. +[package.metadata.leptos] +output-name = "rust-ipam" # Nom de base des fichiers .wasm et .js générés +site-root = "target/site" # Dossier racine des fichiers compilés par trunk +site-pkg-dir = "pkg" # Sous-dossier des assets WASM/JS dans site-root +site-addr = "127.0.0.1:3000" # Adresse d'écoute du serveur Axum +reload-port = 3001 # Port WebSocket pour le hot-reload en développement + # Profil de compilation WASM optimisé pour réduire la taille du fichier .wasm # Un fichier WASM plus petit = page qui charge plus vite [profile.wasm-release] diff --git a/src/app.rs b/src/app.rs index 3a033d4..3484308 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,10 +1,8 @@ -// app.rs — Composant racine de l'application Leptos +// app.rs — Composants racine de l'application Leptos // -// Ce composant est le point d'entrée de toute l'interface. -// Il définit : -// - Les métadonnées globales (title, CSS...) -// - Le routeur : quelle page afficher selon l'URL -// - Les contextes globaux partagés (à ajouter plus tard : auth, thème...) +// Ce fichier contient deux composants : +// - `Shell` : le document HTML complet (head + body) — SSR uniquement +// - `App` : le contenu de la page avec le routeur — partagé SSR + WASM use leptos::prelude::*; use leptos_meta::*; @@ -15,44 +13,88 @@ use leptos_router::{ use crate::client::home::HomePage; -// `#[component]` est un attribut procédural Leptos. -// Il transforme une fonction Rust normale en composant réutilisable et traçable. +// Shell — document HTML complet rendu par le serveur Axum // -// Règle de nommage : toujours PascalCase pour les composants Leptos. +// Ce composant n'existe qu'en mode SSR (`#[cfg(feature = "ssr")]`). +// Il fournit la structure HTML de base que leptos_meta ne peut pas créer seul : +// un et un valides. Sans ça, les composants , <Stylesheet> +// de leptos_meta n'ont nulle part où s'injecter. // -// `-> impl IntoView` : la fonction retourne "quelque chose affichable". -// On utilise `impl` (type opaque) car le type exact généré par `view!` est complexe. +// Flux de rendu SSR : +// 1. Axum appelle Shell() pour chaque requête +// 2. Shell rend le <head> avec MetaTags (placeholder rempli par App) +// 3. Shell rend le <body> contenant App() +// 4. App() appelle provide_meta_context() et définit les métadonnées +// 5. Leptos collecte les métadonnées et les injecte dans MetaTags rétroactivement +// 6. HydrationScripts génère les <script> pour charger le bundle WASM +#[cfg(feature = "ssr")] +#[component] +pub fn Shell( + // LeptosOptions contient la config du projet (chemins, noms de fichiers, ports...) + // Utilisée par HydrationScripts pour construire les URLs du bundle WASM. + options: leptos::config::LeptosOptions, +) -> impl IntoView { + view! { + <!DOCTYPE html> + <html lang="fr"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1"/> + // MetaTags : placeholder où leptos_meta injecte les balises collectées + // depuis les composants <Title>, <Stylesheet>, <Meta>... définis dans App(). + <MetaTags/> + // HydrationScripts : génère les balises <link> et <script> qui chargent + // le bundle WASM compilé par trunk et appellent la fonction hydrate() de lib.rs. + <HydrationScripts options=options.clone()/> + // AutoReload : hot-reload en développement (no-op en production). + // S'active uniquement si la variable d'env LEPTOS_WATCH est définie. + <AutoReload options/> + </head> + <body> + // App() s'exécute ici, fournit le contexte meta et rend le contenu de la page + <App/> + </body> + </html> + } +} + +// App — composant racine partagé entre le serveur (SSR) et le navigateur (WASM) +// +// Ce composant est rendu : +// - côté serveur : dans le <body> du Shell, pour générer le HTML +// - côté navigateur : via hydrate() dans lib.rs, pour attacher la réactivité +// +// `-> impl IntoView` : retourne "quelque chose affichable". Le type exact est opaque +// car le compilateur Leptos génère un type complexe à partir de la macro `view!`. #[component] pub fn App() -> impl IntoView { - // Initialise le contexte de métadonnées Leptos. - // Sans cet appel, les composants <Title>, <Meta>, <Stylesheet> plus bas ne fonctionnent pas. + // Initialise le système de métadonnées Leptos. + // Sans cet appel, <Title>, <Stylesheet>, <Meta> dans les composants enfants + // n'auraient pas de contexte où stocker les métadonnées. provide_meta_context(); - // La macro `view!` permet d'écrire du HTML dans du Rust. - // Leptos la transforme en code Rust pur à la compilation — pas de runtime template engine. view! { - // Définit le titre de l'onglet navigateur (injecté dans le <head> HTML) + // Définit le titre de l'onglet navigateur <Title text="Rust IPAM — Gestionnaire d'adresses IP"/> - // Charge le CSS global. Le nom de fichier suit la convention Leptos : - // `{nom-du-crate}.css` dans le dossier `/pkg/` servi par Axum. + // Charge le CSS global depuis /pkg/rust-ipam.css + // Ce fichier est généré par trunk à partir de style.css (si ajouté plus tard) <Stylesheet id="main" href="/pkg/rust-ipam.css"/> - // Le Router gère la navigation côté client sans rechargement de page. - // Côté serveur (SSR), il détermine quel composant rendre selon l'URL demandée. + // Le Router gère la navigation sans rechargement de page. + // Côté serveur, il détermine quel composant rendre selon l'URL. <Router> <main> - // `<Routes>` est le conteneur pour toutes les définitions de routes. - // `fallback` est affiché si aucune route ne correspond à l'URL actuelle. + // <Routes> est le conteneur pour toutes les définitions de routes. + // `fallback` est affiché si aucune route ne correspond. <Routes fallback=|| view! { <div class="page-erreur"> <h1>"404 — Page introuvable"</h1> <a href="/">"← Retour à l'accueil"</a> </div> }> - // Chaque `<Route>` associe un chemin URL à un composant. - // `path!(/)` correspond exactement à l'URL racine "/". - // Ajouter de nouvelles pages ici, exemple : + // path!(/) correspond à l'URL racine "/" + // Ajouter de nouvelles pages ici : // <Route path=path!("/reseaux") view=ReseauxPage/> <Route path=path!("/") view=HomePage/> </Routes> diff --git a/src/main.rs b/src/main.rs index 37bcd88..5d368fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,75 +12,73 @@ #[cfg(feature = "ssr")] #[tokio::main] -// `#[tokio::main]` est une macro qui transforme notre `fn main()` synchrone -// en une fonction asynchrone, gérée par le runtime Tokio. -// Sans ça, Rust ne saurait pas comment exécuter du code `async`. +// `#[tokio::main]` transforme `fn main()` synchrone en une fonction asynchrone, +// gérée par le runtime Tokio. Sans ça, Rust ne sait pas exécuter du code `async`. async fn main() { use axum::Router; use leptos::config::get_configuration; use leptos_axum::{generate_route_list, LeptosRoutes}; - use rust_ipam::{app::App, server::routes::not_found_handler}; + use leptos::view; + use rust_ipam::{ + app::{App, Shell}, + server::routes::not_found_handler, + }; use tower_http::services::ServeDir; - // Initialise le système de logs structurés. - // Les macros tracing::info!(), tracing::warn!(), tracing::error!() - // n'affichent rien sans cet initialisateur. + // Initialise les logs structurés. + // tracing::info!(), tracing::warn!(), etc. n'affichent rien sans cet initialisateur. + // RUST_LOG=debug cargo run --features ssr → active les logs debug tracing_subscriber::fmt() .with_env_filter( - // Lire le niveau de log depuis la variable d'environnement RUST_LOG, - // ou utiliser "info" par défaut si elle n'est pas définie. - // `unwrap_or_else` est une alternative idiomatique à `unwrap()` : - // elle fournit une valeur de repli au lieu de paniquer. std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), ) .init(); tracing::info!("Démarrage du serveur Rust IPAM..."); - // Charge la configuration Leptos. - // Leptos peut lire un fichier `Leptos.toml` ou utiliser des valeurs par défaut. - // On utilise `.expect()` car le serveur ne peut pas fonctionner sans configuration. - // La chaîne passée à expect() est affichée si la valeur est Err ou None. - // Note : get_configuration() est synchrone en Leptos 0.7 — pas de .await ici. - let conf = get_configuration(None) - .expect("Impossible de charger la configuration Leptos"); + // `Some("Cargo.toml")` indique à Leptos de lire la section + // [package.metadata.leptos] du Cargo.toml pour la configuration + // (noms de fichiers, chemins, adresse serveur...). + let conf = get_configuration(Some("Cargo.toml")) + .expect("Impossible de charger la configuration Leptos depuis Cargo.toml"); let leptos_options = conf.leptos_options; let addr = leptos_options.site_addr; - // Analyse statiquement tous les composants `<Route>` dans `App` - // pour construire la liste des URLs que Leptos doit gérer. + // Analyse les composants `<Route>` dans `App` pour construire + // la liste des URLs que Leptos SSR doit gérer. let routes = generate_route_list(App); - // Construit le routeur Axum avec le pattern builder. - // Chaque méthode retourne un nouveau Router modifié — c'est du chaînage fonctionnel. + // Construit le routeur Axum avec le pattern builder (chaînage de méthodes). let app = Router::new() - // Sert les fichiers statiques compilés par trunk (WASM, CSS, JS...). - // `trunk build` les place dans `target/site/pkg/`. - // Les navigateurs les demandent via des URLs comme `/pkg/rust-ipam.wasm`. + // Sert les fichiers statiques compilés par trunk (WASM, JS...). + // Trunk les place dans target/site/pkg/ (configuré dans [package.metadata.leptos]). .nest_service("/pkg", ServeDir::new("target/site/pkg")) - // Branche toutes les routes Leptos dans Axum. - // Pour chaque URL dans `routes`, Axum rend le composant App() en HTML. - .leptos_routes(&leptos_options, routes, App) - // Handler de repli : toute URL non reconnue retourne une 404. + // Branche les routes Leptos dans Axum. + // Pour chaque URL, Axum rend Shell() en HTML et le renvoie au navigateur. + // Shell() contient App() qui fournit le contenu de la page. + .leptos_routes(&leptos_options, routes, { + // On clone les options pour les capturer dans la closure. + // Le `move` transfère la propriété de `leptos_options` dans la closure. + let leptos_options = leptos_options.clone(); + move || view! { <Shell options=leptos_options.clone()/> } + }) .fallback(not_found_handler) // Partage les options Leptos avec tous les handlers via le système d'état Axum. .with_state(leptos_options); - // Crée un listener TCP sur l'adresse configurée (par défaut 127.0.0.1:3000). let listener = tokio::net::TcpListener::bind(&addr) .await .expect(&format!("Impossible d'écouter sur l'adresse {}", addr)); tracing::info!("Serveur disponible sur http://{}", addr); - // Lance le serveur. Cette ligne bloque jusqu'à un Ctrl+C. axum::serve(listener, app) .await .expect("Erreur critique du serveur"); } -// Ce bloc vide est nécessaire pour que le compilateur trouve un `fn main()` -// quand on compile en mode WASM (où la feature "ssr" n'est pas activée). +// Ce bloc vide est obligatoire pour que le compilateur trouve un `fn main()` +// en mode WASM (où la feature "ssr" n'est pas activée). // En WASM, le vrai point d'entrée est la fonction `hydrate()` dans lib.rs. #[cfg(not(feature = "ssr"))] fn main() {}