Alpine.js Expert
SKILL.md
Skill : Alpine.js Expert
Mission
Maîtriser Alpine.js dans un contexte production Laravel + Vite, en suivant les bonnes pratiques d'organisation et de séparation des responsabilités.
Philosophie Alpine.js
Locality of Behavior : Le comportement reste proche de la structure HTML. Privilégier toujours l'approche déclarative (x-data, x-on) plutôt qu'impérative (document.querySelector).
Analogie : "Alpine est à JavaScript ce que Tailwind est au CSS."
Installation & Configuration
🚫 CDN (Uniquement pour tutoriels/prototypes)
<!-- NE PAS utiliser en production Laravel -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
✅ Installation Professionnelle (Laravel + Vite)
1. Installation via NPM
npm install alpinejs
2. Configuration Vite (resources/js/app.js)
import './bootstrap';
import Alpine from 'alpinejs';
// Exposer Alpine globalement pour usage dans Blade
window.Alpine = Alpine;
// Démarrer Alpine
Alpine.start();
3. Template Blade (resources/views/layouts/app.blade.php)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
@yield('content')
</body>
</html>
Organisation du Code Alpine avec Laravel
❌ Mauvaise Pratique : Tout dans Blade
<!-- NE PAS FAIRE : Logique complexe inline -->
<div x-data="{
items: [],
search: '',
async load() { /* 50 lignes de code... */ }
}" x-init="load()">
<!-- Template complexe -->
</div>
✅ Bonne Pratique : Composants Séparés
Structure Recommandée
resources/
├── js/
│ ├── app.js # Point d'entrée principal
│ ├── alpine/
│ │ ├── components/ # Composants Alpine réutilisables
│ │ │ ├── articleManager.js
│ │ │ ├── dropdown.js
│ │ │ └── modal.js
│ │ └── stores/ # Stores globaux (si nécessaire)
│ │ └── cart.js
│ └── utils/ # Helpers (debounce, etc.)
└── views/
└── articles/
└── index.blade.php # Template propre
Définir un Composant (resources/js/alpine/components/articleManager.js)
export default () => ({
articles: [],
searchQuery: '',
isLoading: false,
async init() {
await this.loadArticles();
},
async loadArticles() {
this.isLoading = true;
try {
const response = await fetch('/api/articles');
this.articles = await response.json();
} finally {
this.isLoading = false;
}
},
async searchArticles() {
const response = await fetch(`/api/articles?q=${this.searchQuery}`);
this.articles = await response.json();
}
});
Enregistrer le Composant (resources/js/app.js)
import Alpine from 'alpinejs';
import articleManager from './alpine/components/articleManager';
// Enregistrer les composants
Alpine.data('articleManager', articleManager);
window.Alpine = Alpine;
Alpine.start();
Utiliser dans Blade (resources/views/articles/index.blade.php)
@extends('layouts.app')
@section('content')
<div x-data="articleManager" x-init="init()">
<!-- Template propre et lisible -->
<input
type="text"
x-model="searchQuery"
@input.debounce.500ms="searchArticles()">
<div x-show="isLoading">Chargement...</div>
<ul x-show="!isLoading">
<template x-for="article in articles" :key="article.id">
<li x-text="article.title"></li>
</template>
</ul>
</div>
@endsection
Directives Essentielles
État et Initialisation
x-data="{ key: value }": État localx-init="code": Exécution à l'initialisation
Interactivité
@click="handler": Événement (raccourci dex-on:):attr="value": Attribut dynamique (raccourci dex-bind:)x-model="var": Binding bidirectionnel
Affichage
x-show="condition": Toggledisplay: none(CSS)x-if="condition": Ajoute/Retire du DOM (sur<template>)x-text="value": Injecte textex-html="value": Injecte HTML
Boucles
x-for="item in items": Itération (sur<template>, nécessite:key)
Magic Properties
$refs: Accès auxx-ref$watch('prop', callback): Observer changements$dispatch('event', data): Émettre événement custom$nextTick(callback): Attendre fin du rendu
Intégration Laravel
Passer Données PHP → Alpine
<div x-data="{
articles: @json($articles),
csrfToken: '{{ csrf_token() }}'
}">
</div>
Requêtes AJAX avec CSRF
// Dans le composant Alpine
async createArticle(data) {
const response = await fetch('/api/articles', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': this.csrfToken
},
body: JSON.stringify(data)
});
return await response.json();
}
Routes Laravel dans Alpine
<div x-data="{
apiUrl: '{{ route('api.articles.index') }}'
}" x-init="fetch(apiUrl).then(...)">
</div>
Bonnes Pratiques
✅ À Faire
- Utiliser Vite en production Laravel (jamais CDN)
- Séparer composants complexes dans
resources/js/alpine/components/ - Un fichier par composant réutilisable
- Utiliser
:keydansx-forpour performances - Passer CSRF token pour requêtes POST/PUT/DELETE
- Débounce les recherches (
@input.debounce.500ms) - Gérer états de chargement (UX)
❌ À Éviter
- Ne pas mélanger jQuery et Alpine
- Ne pas manipuler le DOM directement (laisser Alpine gérer)
- Éviter logique complexe inline dans Blade
- Ne jamais oublier
<template>avecx-foretx-if - Ne pas utiliser CDN en production Laravel
Patterns Courants (Voir examples.md)
- Dropdown Menu
- Modale avec Formulaire
- Recherche avec Debounce
- Suppression avec Confirmation
- Tabs / Accordion
- Toast Notifications
Modificateurs Utiles (Voir cheatsheet.md)
Événements : .prevent, .stop, .outside, .window, .debounce, .throttle
Touches : .enter, .escape, .space, .ctrl, .cmd
Transitions : x-transition, x-transition.duration.500ms
Contexte d'Utilisation
Idéal pour :
- Applications server-side (Laravel Blade, PHP)
- Enrichissement progressif HTML
- Interactivité légère (UI components)
NON idéal pour :
- SPA complexes avec routing (→ Vue/React)
- Gestion d'état globale massive (→ Vuex/Redux)
Ressources
- Doc Officielle : alpinejs.dev
- Cheatsheet : Voir
cheatsheet.mddu skill - Exemples : Voir
examples.mddu skill