feat(perf): performance composables for lazy loading, dedup, prefetch, cleanup (M18.1-M18.8)
- Image lazy loading: IntersectionObserver + blur-up placeholder - Request deduplication: in-flight Promise sharing by URL+body key - Prefetch on hover: 5-minute cache for pre-fetched detail data - Memory leak audit: useCleanup() tracks intervals/listeners/observers - Background sync queue: retry failed IDB saves on visibility change - Bundle splitting: composable architecture enables tree-shaking Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
dd7b694dee
commit
1664fbbb6f
@@ -0,0 +1,90 @@
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const STORAGE_KEY = 'aiui-sync-queue'
|
||||
|
||||
interface QueuedOperation {
|
||||
id: string
|
||||
type: string
|
||||
data: unknown
|
||||
createdAt: number
|
||||
}
|
||||
|
||||
const queue = ref<QueuedOperation[]>([])
|
||||
const hasQueuedItems = ref(false)
|
||||
|
||||
function loadQueue() {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) queue.value = JSON.parse(stored)
|
||||
hasQueuedItems.value = queue.value.length > 0
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function saveQueue() {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(queue.value))
|
||||
hasQueuedItems.value = queue.value.length > 0
|
||||
}
|
||||
|
||||
loadQueue()
|
||||
|
||||
export function useSyncQueue() {
|
||||
function enqueue(type: string, data: unknown) {
|
||||
queue.value.push({
|
||||
id: crypto.randomUUID(),
|
||||
type,
|
||||
data,
|
||||
createdAt: Date.now(),
|
||||
})
|
||||
saveQueue()
|
||||
}
|
||||
|
||||
async function processQueue(handler: (op: QueuedOperation) => Promise<boolean>) {
|
||||
const remaining: QueuedOperation[] = []
|
||||
for (const op of queue.value) {
|
||||
try {
|
||||
const success = await handler(op)
|
||||
if (!success) remaining.push(op)
|
||||
} catch {
|
||||
remaining.push(op)
|
||||
}
|
||||
}
|
||||
queue.value = remaining
|
||||
saveQueue()
|
||||
}
|
||||
|
||||
function clearQueue() {
|
||||
queue.value = []
|
||||
saveQueue()
|
||||
}
|
||||
|
||||
// Auto-retry on visibility change
|
||||
let retryHandler: (() => void) | null = null
|
||||
|
||||
function startAutoRetry(handler: (op: QueuedOperation) => Promise<boolean>) {
|
||||
retryHandler = () => {
|
||||
if (document.visibilityState === 'visible' && queue.value.length > 0) {
|
||||
processQueue(handler)
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', retryHandler)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Already loaded
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (retryHandler) {
|
||||
document.removeEventListener('visibilitychange', retryHandler)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
queue,
|
||||
hasQueuedItems,
|
||||
enqueue,
|
||||
processQueue,
|
||||
clearQueue,
|
||||
startAutoRetry,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user