تطبيقات الويب التقدمية

استراتيجيات التخزين المؤقت

20 دقيقة الدرس 5 من 30

فهم استراتيجيات التخزين المؤقت

استراتيجيات التخزين المؤقت تحدد كيفية استجابة service worker لطلبات الشبكة. استراتيجيات مختلفة تعمل بشكل أفضل لأنواع مختلفة من الموارد. الاستراتيجية الصحيحة يمكن أن تحسن الأداء بشكل كبير وتمكّن وظائف العمل بدون إنترنت.

لماذا استراتيجيات متعددة؟

لا توجد استراتيجية تخزين مؤقت واحدة تناسب جميع الموارد:

  • الأصول الثابتة (CSS, JS) نادراً ما تتغير - ذاكرة التخزين أولاً
  • بيانات API تتغير بشكل متكرر - الشبكة أولاً
  • الصور كبيرة لكن ثابتة - ذاكرة التخزين أولاً مع احتياطي
  • محتوى المستخدم يجب أن يكون محدثاً - الشبكة فقط أو الشبكة أولاً

1. ذاكرة التخزين أولاً (Cache First)

تحقق من ذاكرة التخزين أولاً، جلب من الشبكة فقط إذا لم يكن مخزناً. الأفضل للأصول الثابتة التي نادراً ما تتغير.

// sw.js - استراتيجية ذاكرة التخزين أولاً self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(cachedResponse => { if (cachedResponse) { return cachedResponse; } return fetch(event.request); }) ); });
ذاكرة التخزين أولاً - الأفضل لـ:
  • ملفات CSS وJavaScript الثابتة
  • الصور والخطوط
  • قوالب HTML (إذا كنت تستخدم SPA)
  • أي أصول بإصدارات (app-v1.2.3.js)
الإيجابيات: أسرع استجابة، يعمل بدون إنترنت، يقلل النطاق الترددي
السلبيات: قد يقدم محتوى قديم

2. الشبكة أولاً (Network First)

// sw.js - استراتيجية الشبكة أولاً self.addEventListener('fetch', event => { event.respondWith( fetch(event.request) .then(response => { return caches.open(CACHE_NAME).then(cache => { cache.put(event.request, response.clone()); return response; }); }) .catch(() => caches.match(event.request)) ); });
الشبكة أولاً - الأفضل لـ: استجابات API، البيانات الديناميكية، موجزات الأخبار
الإيجابيات: دائماً محدث عند الاتصال
السلبيات: أبطأ، يستخدم النطاق الترددي

3. قديم أثناء إعادة التحقق (Stale While Revalidate)

// sw.js - قديم أثناء إعادة التحقق self.addEventListener('fetch', event => { event.respondWith( caches.open(CACHE_NAME).then(cache => { return cache.match(event.request).then(response => { const fetchPromise = fetch(event.request).then(networkResponse => { cache.put(event.request, networkResponse.clone()); return networkResponse; }); return response || fetchPromise; }); }) ); });
أفضل توازن: يرجع المخزن فوراً، يحدث في الخلفية

4. الشبكة فقط وذاكرة التخزين فقط

// الشبكة فقط (للمصادقة، المدفوعات) event.respondWith(fetch(event.request)); // ذاكرة التخزين فقط (نادراً ما تُستخدم) event.respondWith(caches.match(event.request));

اختيار الاستراتيجية الصحيحة

المورد الاستراتيجية
CSS/JS/الخطوط ذاكرة التخزين أولاً
بيانات API الشبكة أولاً
الصور قديم أثناء إعادة التحقق
المصادقة الشبكة فقط

مثال كامل لاستراتيجيات متعددة

// sw.js - استراتيجيات مدمجة const CACHE_NAME = 'pwa-v1'; self.addEventListener('fetch', event => { const url = new URL(event.request.url); // الشبكة فقط - المصادقة if (url.pathname.startsWith('/api/auth')) { event.respondWith(fetch(event.request)); return; } // الشبكة أولاً - API if (url.pathname.startsWith('/api/')) { event.respondWith( fetch(event.request) .then(response => { caches.open(CACHE_NAME).then(cache => { cache.put(event.request, response.clone()); }); return response; }) .catch(() => caches.match(event.request)) ); return; } // قديم أثناء إعادة التحقق - الصور if (event.request.destination === 'image') { event.respondWith( caches.match(event.request).then(cachedResponse => { const fetchPromise = fetch(event.request).then(response => { caches.open(CACHE_NAME).then(cache => { cache.put(event.request, response.clone()); }); return response; }); return cachedResponse || fetchPromise; }) ); return; } // ذاكرة التخزين أولاً - كل شيء آخر event.respondWith( caches.match(event.request) .then(response => response || fetch(event.request)) ); });

إدارة ذاكرة التخزين

// تحديد حجم ذاكرة التخزين const MAX_CACHE_SIZE = 50; async function trimCache(cacheName, maxItems) { const cache = await caches.open(cacheName); const keys = await cache.keys(); if (keys.length > maxItems) { await cache.delete(keys[0]); await trimCache(cacheName, maxItems); } } // تنظيف ذاكرات التخزين القديمة عند التفعيل self.addEventListener('activate', event => { event.waitUntil( caches.keys().then(names => { return Promise.all( names .filter(name => name !== CACHE_NAME) .map(name => caches.delete(name)) ); }) ); });

احتياطي بدون إنترنت

// تخزين صفحة بدون إنترنت أثناء التثبيت const OFFLINE_PAGE = '/offline.html'; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => cache.add(OFFLINE_PAGE)) ); }); // عرض صفحة بدون إنترنت عند فشل التنقل self.addEventListener('fetch', event => { if (event.request.mode === 'navigate') { event.respondWith( fetch(event.request) .catch(() => caches.match(OFFLINE_PAGE)) ); } });
حدود تخزين ذاكرة التخزين:
  • Chrome: حتى 60% من مساحة القرص
  • Firefox: حتى 50% من المساحة الحرة
  • Safari: حتى 50MB (يمكن طلب المزيد)
التحقق من الاستخدام: navigator.storage.estimate()
تمرين:
  1. أنشئ service worker مع جميع استراتيجيات التخزين المؤقت الخمس
  2. طبّق Cache First لملفات CSS/JS
  3. طبّق Network First لنقاط نهاية /api/*
  4. طبّق Stale While Revalidate للصور
  5. نفّذ حد حجم ذاكرة التخزين (حد أقصى 20 عنصر)
  6. أضف صفحة احتياطية بدون إنترنت
  7. اختبر باستخدام تبويب Network في Chrome DevTools (وضع Offline)
  8. افحص Cache Storage في تبويب Application
  9. قس استخدام ذاكرة التخزين باستخدام navigator.storage.estimate()