Files
thrillwiki_django_no_react/memory-bank/research/alpine-optimization-strategies.md

459 lines
10 KiB
Markdown

# Alpine.js Optimization Strategies and Best Practices
## Research Summary
Comprehensive research from Alpine.js documentation focusing on performance optimization, component patterns, and best practices for the ThrillWiki frontend redesign.
## Performance Optimization Strategies
### 1. Component Initialization and Lifecycle
#### Efficient Component Registration
```javascript
document.addEventListener('alpine:init', () => {
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = !this.open
},
destroy() {
// Clean up resources to prevent memory leaks
clearInterval(this.timer);
}
}))
})
```
#### Performance Measurement
```javascript
window.start = performance.now();
document.addEventListener('alpine:initialized', () => {
setTimeout(() => {
console.log(performance.now() - window.start);
});
});
```
### 2. Event Handling Optimization
#### Use .passive Modifier for Scroll Performance
```html
<div @touchstart.passive="...">...</div>
<div @wheel.passive="...">...</div>
```
**Critical**: The `.passive` modifier prevents blocking the browser's scroll optimizations by indicating the listener won't call `preventDefault()`.
#### Debounced Event Handling
```html
<input @input.debounce.300ms="search()" placeholder="Search...">
```
#### Efficient Event Delegation
```html
<div @click.outside="open = false" x-data="{ open: false }">
<!-- Content -->
</div>
```
### 3. Data Management Optimization
#### Minimize Reactive Data
```javascript
Alpine.data('app', () => ({
// Only make data reactive if it needs to trigger UI updates
items: [], // Reactive - triggers UI updates
_cache: {}, // Non-reactive - use for internal state
get filteredItems() {
// Use getters for computed properties
return this.items.filter(item => item.active)
}
}))
```
#### Efficient Array Operations
```javascript
// Good: Use array methods that don't trigger full re-renders
this.items.splice(index, 1); // Remove specific item
this.items.push(newItem); // Add item
// Avoid: Full array replacement when possible
// this.items = this.items.filter(...) // Triggers full re-render
```
### 4. DOM Manipulation Optimization
#### Use x-show vs x-if Strategically
```html
<!-- Use x-show for frequently toggled content -->
<div x-show="isVisible" x-transition>
Frequently toggled content
</div>
<!-- Use x-if for conditionally rendered content -->
<template x-if="shouldRender">
<div>Rarely shown content</div>
</template>
```
#### Optimize x-for Loops
```html
<!-- Always use :key for efficient list updates -->
<template x-for="item in items" :key="item.id">
<div x-text="item.name"></div>
</template>
```
#### Minimize DOM Queries
```javascript
Alpine.data('component', () => ({
init() {
// Cache DOM references in init()
this.element = this.$el;
this.container = this.$refs.container;
}
}))
```
### 5. Memory Management
#### Proper Cleanup in destroy()
```javascript
Alpine.data('timer', () => ({
timer: null,
counter: 0,
init() {
this.timer = setInterval(() => {
console.log('Increased counter to', ++this.counter);
}, 1000);
},
destroy() {
// Critical: Clean up to prevent memory leaks
clearInterval(this.timer);
}
}))
```
#### Avoid Memory Leaks in Event Listeners
```javascript
Alpine.data('component', () => ({
init() {
// Use arrow functions to maintain context
this.handleResize = () => this.onResize();
window.addEventListener('resize', this.handleResize);
},
destroy() {
window.removeEventListener('resize', this.handleResize);
}
}))
```
## Component Architecture Patterns
### 1. Reusable Component Registration
#### Global Component Registration
```javascript
Alpine.data('dropdown', () => ({
open: false,
toggle() {
this.open = !this.open
},
// Encapsulate directive logic
trigger: {
['@click']() {
this.toggle()
}
},
dialogue: {
['x-show']() {
return this.open
}
}
}))
```
#### Usage in Templates
```html
<div x-data="dropdown">
<button x-bind="trigger">Toggle</button>
<div x-bind="dialogue">Content</div>
</div>
```
### 2. State Management Patterns
#### Hierarchical Data Access
```html
<div x-data="{ open: false }">
<div x-data="{ label: 'Content:' }">
<span x-text="label"></span>
<span x-show="open"></span> <!-- Accesses parent's open -->
</div>
</div>
```
#### Centralized State with $store
```javascript
Alpine.store('app', {
user: null,
parks: [],
setUser(user) {
this.user = user
},
addPark(park) {
this.parks.push(park)
}
})
```
### 3. Advanced Interaction Patterns
#### Custom Event Dispatching
```html
<div @park-created="handleParkCreated">
<button @click="$dispatch('park-created', { park: newPark })">
Create Park
</button>
</div>
```
#### Intersection Observer Integration
```html
<div x-data="{ shown: false }" x-intersect="shown = true">
<div x-show="shown" x-transition>
I'm in the viewport!
</div>
</div>
```
#### Watch for Reactive Updates
```html
<div x-data="{ search: '' }"
x-init="$watch('search', value => console.log('Search:', value))">
<input x-model="search" placeholder="Search...">
</div>
```
## Integration with HTMX
### 1. Complementary Usage Patterns
#### HTMX for Server Communication, Alpine for Client State
```html
<div x-data="{ loading: false, count: 0 }">
<button hx-post="/increment"
hx-target="#result"
@click="loading = true; count++"
@htmx:after-request="loading = false">
<span x-show="!loading">Increment</span>
<span x-show="loading">Loading...</span>
</button>
<div id="result" x-text="'Local count: ' + count"></div>
</div>
```
#### Coordinated State Updates
```html
<div x-data="{ items: [] }"
@item-added.window="items.push($event.detail)">
<form hx-post="/items"
hx-target="#items-list"
@htmx:after-request="$dispatch('item-added', { item: newItem })">
<!-- Form fields -->
</form>
<div id="items-list"></div>
</div>
```
### 2. Performance Coordination
#### Efficient DOM Updates
```html
<!-- Use Alpine for immediate feedback -->
<div x-data="{ optimisticUpdate: false }">
<button @click="optimisticUpdate = true"
hx-post="/action"
hx-target="#result"
@htmx:after-request="optimisticUpdate = false">
<span x-show="!optimisticUpdate">Click me</span>
<span x-show="optimisticUpdate">Processing...</span>
</button>
</div>
```
## ThrillWiki-Specific Optimizations
### 1. Search Component Optimization
```javascript
Alpine.data('parkSearch', () => ({
query: '',
results: [],
loading: false,
async search() {
if (!this.query.trim()) {
this.results = [];
return;
}
this.loading = true;
try {
// Use HTMX for actual search, Alpine for state
const response = await fetch(`/search/parks/?q=${encodeURIComponent(this.query)}`);
this.results = await response.json();
} catch (error) {
console.error('Search failed:', error);
this.results = [];
} finally {
this.loading = false;
}
},
get filteredResults() {
return this.results.slice(0, 10); // Limit results for performance
}
}))
```
### 2. Photo Gallery Optimization
```javascript
Alpine.data('photoGallery', () => ({
photos: [],
currentIndex: 0,
loading: false,
init() {
// Lazy load images as they come into view
this.$nextTick(() => {
this.setupIntersectionObserver();
});
},
setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
this.$el.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
},
destroy() {
// Clean up observer
if (this.observer) {
this.observer.disconnect();
}
}
}))
```
### 3. Form Validation Optimization
```javascript
Alpine.data('parkForm', () => ({
form: {
name: '',
location: '',
operator: ''
},
errors: {},
validating: false,
async validateField(field) {
if (this.validating) return;
this.validating = true;
try {
// Use HTMX for server-side validation
const response = await fetch('/validate-park-field/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken()
},
body: JSON.stringify({
field: field,
value: this.form[field]
})
});
const result = await response.json();
if (result.errors) {
this.errors[field] = result.errors;
} else {
delete this.errors[field];
}
} finally {
this.validating = false;
}
},
getCsrfToken() {
return document.querySelector('[name=csrfmiddlewaretoken]').value;
}
}))
```
## Performance Monitoring
### 1. Component Performance Tracking
```javascript
Alpine.data('performanceTracker', () => ({
init() {
const start = performance.now();
this.$nextTick(() => {
const end = performance.now();
console.log(`Component initialized in ${end - start}ms`);
});
}
}))
```
### 2. Memory Usage Monitoring
```javascript
// Monitor component count and memory usage
setInterval(() => {
if (performance.memory) {
console.log('Memory usage:', {
used: Math.round(performance.memory.usedJSHeapSize / 1048576) + 'MB',
total: Math.round(performance.memory.totalJSHeapSize / 1048576) + 'MB'
});
}
}, 10000);
```
## Implementation Priorities for ThrillWiki
### High Priority
1. **Component Registration**: Set up reusable components for common UI patterns
2. **Event Optimization**: Use .passive modifiers for scroll events
3. **Memory Management**: Implement proper cleanup in destroy() methods
4. **State Management**: Optimize reactive data usage
### Medium Priority
1. **Intersection Observer**: Lazy loading for images and content
2. **Performance Monitoring**: Track component initialization times
3. **Advanced Patterns**: Custom event dispatching and coordination
4. **Search Optimization**: Debounced search with result limiting
### Low Priority
1. **Advanced State Management**: Global stores for complex state
2. **Custom Directives**: Create project-specific Alpine directives
3. **Performance Profiling**: Detailed memory and performance analysis