Cours Svelte et SvelteKit · Module 2: Composants
Organisation du projet
Dans cette leçon4
Un projet bien organisé, c'est comme un village viking bien organisé : tout le monde sait où se trouve chaque chose !
Structure recommandée
src/
├── lib/ # Code partagé (alias #lib)
│ ├── components/ # Composants d'interface
│ │ ├── ui/ # De base (Button, Input)
│ │ │ ├── Button.svelte
│ │ │ └── Input.svelte
│ │ ├── layout/ # Mises en page (Header, Footer)
│ │ │ ├── Header.svelte
│ │ │ └── Footer.svelte
│ │ └── features/ # Fonctionnels
│ │ ├── WarriorCard.svelte
│ │ └── ShipList.svelte
│ ├── state/ # État partagé (runes dans des fichiers .svelte.js)
│ │ ├── warriors.svelte.js
│ │ └── ships.svelte.js
│ ├── utils/ # Fonctions utilitaires
│ │ ├── formatters.js
│ │ └── validators.js
│ └── types/ # Types TypeScript
│ └── index.ts
├── routes/ # Pages (SvelteKit)
│ ├── +page.svelte
│ ├── +layout.svelte
│ └── warriors/
│ └── +page.svelte
└── app.html # Template HTMLLe répertoire state/ contient l'état partagé par plusieurs composants. En Svelte 5, ce sont de simples fichiers .svelte.js dans lesquels les runes comme $state fonctionnent. Les projets plus anciens ont ici un répertoire stores/ avec des stores Svelte : tu les découvriras au Puits de Mimir (module 8).
Alias d'import
Un projet créé avec npx sv create possède l'alias #lib dans package.json, qui pointe vers le répertoire src/lib. C'est une fonctionnalité de Node.js appelée subpath imports (le champ imports) :
{
"imports": {
"#lib": "./src/lib/index.js",
"#lib/*": "./src/lib/*"
}
}Grâce à lui, plus besoin de compter les ../ dans les chemins. N'oublie simplement pas l'extension du fichier à la fin de l'import (.svelte, .js) :
<script>
// Au lieu de : import Button from '../../../lib/components/ui/Button.svelte'
import Button from '#lib/components/ui/Button.svelte';
// État partagé
import { army } from '#lib/state/warriors.svelte.js';
// Utilitaires
import { formatDate } from '#lib/utils/formatters.js';
</script>Dans SvelteKit 2, ce même alias s'appelait $lib et fonctionnait sans extensions : tu le verras dans du code plus ancien. SvelteKit 3 ne le crée plus.
Conventions de nommage
Fichiers de composants
- PascalCase :
WarriorCard.svelte,ShipList.svelte - Le nom = ce que le composant représente
Fichiers utilitaires
- camelCase ou kebab-case :
formatters.js,api-client.ts
Répertoires
- kebab-case :
viking-camp/,ui-components/
Exemple : Composant avec structure complète
Ce composant réunit tout au même endroit. Le parent lui passe un guerrier et une fonction onselect, que le composant appelle quand quelqu'un choisit la carte. C'est une callback prop : en Svelte 5, les composants parlent à leur parent grâce à des fonctions dans les props (plus de détails à Asgard, module 4). Dans un projet TypeScript, tu importes aussi les fichiers .ts avec l'extension .js : TypeScript trouve tout seul le fichier index.ts.
<!-- src/lib/components/features/WarriorCard.svelte -->
<script lang="ts">
// Imports
import Avatar from '#lib/components/ui/Avatar.svelte';
import { formatHealth } from '#lib/utils/formatters.js';
import type { Warrior } from '#lib/types/index.js';
// Props - données et callback du parent
interface Props {
warrior: Warrior;
selected?: boolean;
onselect?: (id: number) => void;
}
let { warrior, selected = false, onselect }: Props = $props();
// State
let showDetails = $state(false);
// Derived
let healthStatus = $derived(warrior.health > 50 ? 'healthy' : 'wounded');
</script>
<article
class="warrior-card"
class:selected
class:wounded={healthStatus === 'wounded'}
>
<Avatar src={warrior.avatar} alt={warrior.name} />
<h3>{warrior.name}</h3>
<p>Santé : {formatHealth(warrior.health)}</p>
{#if showDetails}
<div class="details">
<p>Arme : {warrior.weapon}</p>
<p>Rang : {warrior.rank}</p>
</div>
{/if}
<button onclick={() => onselect?.(warrior.id)}>Choisir</button>
<button onclick={() => showDetails = !showDetails}>
{showDetails ? 'Masquer' : 'Détails'}
</button>
</article>
<style>
.warrior-card {
padding: 1rem;
border: 2px solid #444;
border-radius: 8px;
transition: all 0.2s;
}
.warrior-card:hover {
border-color: #d4af37;
}
.warrior-card.selected {
background: rgba(212, 175, 55, 0.1);
border-color: #d4af37;
}
.warrior-card.wounded {
border-color: #8b0000;
}
</style>interface Props décrit ce que le composant accepte : un guerrier obligatoire, un indicateur selected facultatif et une fonction onselect facultative. onselect?.(warrior.id) n'appelle la fonction que si le parent l'a passée. En Svelte 4, on utilisait createEventDispatcher au lieu des callback props : en Svelte 5, c'est déprécié.
Entrées de la documentation utilisées dans cette leçon
Tu as repéré une erreur dans cette leçon ?
Teste tes connaissances
Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.
1. Où, dans un projet SvelteKit, doivent se trouver les composants UI partagés ?
Tâches pratiques dans le jeu
- Clique dans l'ordre
Clique sur les conventions de nommage dans l'ordre : composants, fichiers utilitaires, répertoires