A modern, lightweight web application built with Alpine.js, Vite, and TailwindCSS that demonstrates advanced component architecture patterns, lazy loading, and inter-component communication.
| Technology | Purpose | Version |
|---|---|---|
| Alpine.js 🏔️ | Reactive frontend framework | 3.14.9 |
| Vite ⚡ | Build tool and dev server | 6.3.5 |
| TailwindCSS 🎨 | Utility-first CSS framework | 4.1.10 |
| Alpine Intersect 👁️ | Intersection Observer plugin | 3.14.9 |
| HTML Inject Plugin 🔧 | Component template injection | 1.1.2 |
alpineJS/
├── 📄 index.html # Main entry point
├── 📦 package.json # Dependencies & scripts
├── ⚙️ vite.config.mjs # Vite configuration
└── src/
├── 🎨 assets/
│ ├── css/style.css # Global styles
│ └── js/
│ ├── app.js # Main app initialization
│ └── components/ # Alpine.js components
│ ├── slider.js # Image slider component
│ ├── sliderDisplay.js # Slider status display
│ └── lightbox.js # Image lightbox modal
├── 📊 data/
│ └── slides.json # Slider content data
└── 🧩 parts/
├── header.html # Header template
├── slider.html # Slider template
└── lightbox.html # Lightbox template
All components are registered in src/assets/js/app.js:
import Alpine from 'alpinejs'
import Slider from './components/slider.js'
import SliderDisplay from './components/sliderDisplay.js'
import Lightbox from './components/lightbox.js'
// Register components globally
Alpine.data('slider', Slider)
Alpine.data('sliderDisplay', SliderDisplay)
Alpine.data('lightbox', Lightbox)Each component follows a factory function pattern that returns an Alpine.js data object:
export default function ComponentName(parameters) {
return {
// Reactive properties
property: initialValue,
// Lifecycle method
init() {
// Component initialization logic
},
// Component methods
method() {
// Component functionality
}
};
}Templates are modularized using the HTML Inject Plugin:
- ✅ Separation of Concerns: HTML structure separated from logic
- ✅ Reusability: Components can be used across different pages
- ✅ Maintainability: Each component has its own template file
<!-- index.html -->
<load src="src/parts/slider.html" />
<load src="src/parts/lightbox.html" />The project implements intelligent image lazy loading using Alpine's Intersect plugin:
<img x-intersect.once="$el.src = slide.image"
:alt="slide.title"
class="w-full h-full object-cover">How it works:
- 👀 Intersection Observer: Monitors when image containers enter viewport
- ⚡ On-Demand Loading: Images load only when visible
- 🎯 Once Modifier: Each image loads only once to prevent re-triggering
- 📈 Performance Boost: Reduces initial page load time and bandwidth
Slider data is fetched asynchronously:
async loadSlides() {
try {
const response = await fetch(sliderJson);
this.slides = await response.json();
this.isLoading = false;
} catch (error) {
console.error('Error loading slides:', error);
this.isLoading = false;
}
}Benefits:
- ⏱️ Non-blocking: UI remains responsive during data loading
- 🔄 Loading States: Visual feedback with loading indicators
- 🛡️ Error Handling: Graceful degradation on fetch failures
The project uses Alpine's custom event system for component communication:
// Slider component dispatches events
this.$dispatch('slider-ready', {
current: this.current,
total: this.slides.length
});
this.$dispatch('slider-changed', {
current: nextSlideEq,
total: this.slides.length
});
this.$dispatch('open-lightbox', {
image: this.slides[index]
});Method 1: Component-based listening
// SliderDisplay component
init() {
window.addEventListener('slider-ready', this.handleSliderEvent.bind(this));
window.addEventListener('slider-changed', this.handleSliderEvent.bind(this));
}Method 2: Template-based listening
<div x-data="{ current: 0, total: 0 }"
@slider-ready.window="current = $event.detail.current; total = $event.detail.total"
@slider-changed.window="current = $event.detail.current; total = $event.detail.total">
</div>Method 3: Cross-component communication
<div x-data="lightbox()"
@open-lightbox.window="open($event.detail.image)">
</div>┌─────────────┐ slider-ready ┌──────────────────┐
│ Slider │ ───────────────────► │ SliderDisplay │
│ Component │ │ Component │
│ │ slider-changed │ │
│ │ ───────────────────► │ │
└─────────────┘ └──────────────────┘
│
│ open-lightbox
▼
┌─────────────┐
│ Lightbox │
│ Component │
└─────────────┘
- 🎯 Publisher-Subscriber: Slider broadcasts state changes
- 🎭 Command Pattern: Lightbox receives open commands
- 📊 State Synchronization: Multiple displays stay in sync
- 🔗 Loose Coupling: Components don't directly reference each other
- Node.js (v18+)
- npm or yarn
# Clone the repository
git clone https://github.com/Loubal70/AlpinePerfFormation
cd alpineJS
# Install dependencies
npm install
# or
yarn install# Start development server
npm run dev
# or
yarn dev# Build for production
npm run build
# or
yarn build- ⚡ Smooth Transitions: CSS-based animations
- 🖱️ Multiple Controls: Arrows, dots, and keyboard navigation
- 📱 Responsive Design: Mobile-friendly interface
- 🔄 Auto-loop: Infinite scrolling capability
- ⌨️ Keyboard Support: Space bar to close
- 🎭 Elegant Animations: Smooth open/close transitions
- 📝 Content Display: Title and description overlay
- 🚫 Body Scroll Lock: Prevents background scrolling
- 🔄 Real-time Updates: Shows current slide position
- 📡 Event-driven: Updates via custom events
- 🎯 Multiple Instances: Different display methods demonstrated
- ⚡ Performance: Lazy loading and minimal bundle size
- 🧩 Modularity: Component-based architecture
- 🔄 Reactivity: Real-time UI updates
- 📱 Responsive: Mobile-first design approach
- 🛠️ Developer Experience: Hot reload and modern tooling
- 🎨 Maintainable: Clean separation of concerns
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
This project is open source and available under the MIT License.
Built with ❤️ using Alpine.js and modern web technologies