Files
michaelschiemer/docs/livecomponents/island-directive.md
2025-11-24 21:28:25 +01:00

309 lines
8.0 KiB
Markdown

# Island Directive
**Isolated Component Rendering** - Render heavy components separately for better performance.
## Overview
The `#[Island]` directive enables isolated rendering of LiveComponents, allowing them to be rendered separately from the main template flow. This is particularly useful for resource-intensive components that can slow down page rendering.
## Features
- **Isolated Rendering**: Components are rendered separately without template wrapper
- **Lazy Loading**: Optional lazy loading when component enters viewport
- **Independent Caching**: Islands can have their own caching strategies
- **Isolated Events**: Islands don't receive parent component events
## Basic Usage
### Simple Island Component
```php
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Attributes\Island;
#[LiveComponent('heavy-widget')]
#[Island]
final readonly class HeavyWidgetComponent implements LiveComponentContract
{
// Component implementation
}
```
### Lazy Island Component
```php
#[LiveComponent('metrics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading dashboard...')]
final readonly class MetricsDashboardComponent implements LiveComponentContract
{
// Component implementation
}
```
## Configuration Options
### `isolated` (bool, default: `true`)
Whether the component should be rendered in isolation. When `true`, the component is rendered via the `/island` endpoint without template wrapper.
```php
#[Island(isolated: true)] // Isolated rendering (default)
#[Island(isolated: false)] // Normal rendering (not recommended)
```
### `lazy` (bool, default: `false`)
Whether the component should be lazy-loaded when entering the viewport. When `true`, a placeholder is generated and the component is loaded via IntersectionObserver.
```php
#[Island(lazy: true)] // Lazy load on viewport entry
#[Island(lazy: false)] // Load immediately (default)
```
### `placeholder` (string|null, default: `null`)
Custom placeholder text shown while the component is loading (only used when `lazy: true`).
```php
#[Island(lazy: true, placeholder: 'Loading widget...')]
```
## Template Usage
### In Templates
Island components are used the same way as regular LiveComponents:
```html
<x-heavy-widget id="user-123" />
```
When processed, lazy Islands generate a placeholder:
```html
<div data-island-component="true"
data-live-component-lazy="heavy-widget:user-123"
data-lazy-priority="normal"
data-lazy-threshold="0.1">
<div class="island-placeholder">Loading widget...</div>
</div>
```
## Island vs. Lazy Component
### Lazy Component (`data-live-component-lazy`)
- Loaded when entering viewport
- Part of normal template flow
- Shares state/events with parent components
- Uses `/lazy-load` endpoint
### Island Component (`data-island-component`)
- Rendered in isolation (separate request)
- No template wrapper (no layout/meta)
- Isolated event context (no parent events)
- Optional lazy loading on viewport entry
- Uses `/island` endpoint
## Performance Benefits
### Isolation
Islands reduce template processing overhead for heavy components by rendering them separately. This means:
- Heavy components don't block main page rendering
- Independent error handling (one island failure doesn't affect others)
- Separate caching strategies per island
### Lazy Loading
When combined with lazy loading, Islands provide:
- Reduced initial page load time
- Faster Time to Interactive (TTI)
- Better Core Web Vitals scores
### Example Performance Impact
```php
// Without Island: 500ms page load (heavy component blocks rendering)
// With Island: 200ms page load + 300ms island load (non-blocking)
```
## Use Cases
### Heavy Widgets
```php
#[LiveComponent('analytics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading analytics...')]
final readonly class AnalyticsDashboardComponent implements LiveComponentContract
{
// Heavy component with complex data processing
}
```
### Third-Party Integrations
```php
#[LiveComponent('external-map')]
#[Island(isolated: true, lazy: true)]
final readonly class ExternalMapComponent implements LiveComponentContract
{
// Component that loads external resources
}
```
### Conditional Components
```php
#[LiveComponent('admin-panel')]
#[Island(isolated: true)]
final readonly class AdminPanelComponent implements LiveComponentContract
{
// Component only visible to admins
// Isolated to prevent template processing for non-admin users
}
```
## Technical Details
### Endpoint
Islands are rendered via:
```
GET /live-component/{id}/island
```
This endpoint:
- Renders component HTML without template wrapper
- Returns JSON with `html`, `state`, `csrf_token`
- Uses `ComponentRegistry.render()` directly (no wrapper)
### Frontend Integration
Islands are automatically detected and handled by `LazyComponentLoader`:
- Lazy islands: Loaded when entering viewport
- Non-lazy islands: Loaded immediately
- Isolated initialization: Islands don't receive parent events
### Event Isolation
Islands have isolated event contexts:
- No parent component events
- No shared state with parent components
- Independent SSE channels (if configured)
## Best Practices
1. **Use Islands for Heavy Components**: Only use `#[Island]` for components that significantly impact page load time
2. **Combine with Lazy Loading**: Use `lazy: true` for below-the-fold content
3. **Provide Placeholders**: Always provide meaningful placeholder text for better UX
4. **Test Isolation**: Verify that islands work correctly in isolation
5. **Monitor Performance**: Track island load times and optimize as needed
## Examples
### Complete Example
```php
<?php
declare(strict_types=1);
namespace App\Application\LiveComponents\Dashboard;
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Attributes\Island;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;
use App\Framework\LiveComponents\ValueObjects\ComponentState;
#[LiveComponent('metrics-dashboard')]
#[Island(isolated: true, lazy: true, placeholder: 'Loading metrics...')]
final readonly class MetricsDashboardComponent implements LiveComponentContract
{
public function __construct(
public ComponentId $id,
public ComponentState $state
) {
}
public function getRenderData(): ComponentRenderData
{
return new ComponentRenderData(
templatePath: 'livecomponent-metrics-dashboard',
data: [
'componentId' => $this->id->toString(),
'metrics' => $this->loadMetrics(),
]
);
}
private function loadMetrics(): array
{
// Heavy operation - isolated rendering prevents blocking
return [
'users' => 12345,
'orders' => 6789,
'revenue' => 123456.78,
];
}
}
```
## Migration Guide
### Converting Regular Component to Island
1. Add `#[Island]` attribute to component class
2. Test component rendering (should work identically)
3. Optionally enable lazy loading with `lazy: true`
4. Monitor performance improvements
### Backward Compatibility
- Components without `#[Island]` work exactly as before
- `#[Island]` is opt-in (no breaking changes)
- Existing lazy components continue to work
## Troubleshooting
### Island Not Loading
- Check browser console for errors
- Verify component is registered in ComponentRegistry
- Ensure `/island` endpoint is accessible
### Placeholder Not Showing
- Verify `lazy: true` is set
- Check `placeholder` parameter is provided
- Inspect generated HTML for `data-island-component` attribute
### Events Not Working
- Islands have isolated event contexts
- Use component-specific events, not parent events
- Check SSE channel configuration if using real-time updates
## See Also
- [Lazy Loading Guide](livecomponents-lazy-loading.md)
- [Component Caching](livecomponents-caching.md)
- [Performance Optimization](livecomponents-performance.md)