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

بناء مشروع PWA (الجزء الثالث)

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

بناء مشروع PWA (الجزء الثالث)

في هذا الجزء الأخير، سنضيف ميزات متقدمة، ونحسّن الأداء، وننشر تطبيق TaskMaster PWA الخاص بنا، ونجري تدقيقاً كاملاً باستخدام Lighthouse.

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

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

// sw.js - Advanced caching strategies const CACHE_NAME = 'taskmaster-v1'; const STATIC_CACHE = 'taskmaster-static-v1'; const DYNAMIC_CACHE = 'taskmaster-dynamic-v1'; const IMAGE_CACHE = 'taskmaster-images-v1'; const API_CACHE = 'taskmaster-api-v1'; // Cache size limits const CACHE_LIMITS = { [DYNAMIC_CACHE]: 50, [IMAGE_CACHE]: 50, [API_CACHE]: 20 }; // Trim cache to specified limit async function trimCache(cacheName, maxItems) { const cache = await caches.open(cacheName); const keys = await cache.keys(); if (keys.length > maxItems) { const deletePromises = keys .slice(0, keys.length - maxItems) .map(key => cache.delete(key)); await Promise.all(deletePromises); } } // Cache-first strategy (for static assets) async function cacheFirst(request) { const cached = await caches.match(request); return cached || fetch(request); } // Network-first strategy (for API calls) async function networkFirst(request, cacheName) { try { const response = await fetch(request); const cache = await caches.open(cacheName); cache.put(request, response.clone()); await trimCache(cacheName, CACHE_LIMITS[cacheName]); return response; } catch (error) { const cached = await caches.match(request); return cached || Promise.reject(error); } } // Stale-while-revalidate strategy async function staleWhileRevalidate(request, cacheName) { const cached = await caches.match(request); const fetchPromise = fetch(request).then(response => { const cache = caches.open(cacheName); cache.then(c => c.put(request, response.clone())); return response; }); return cached || fetchPromise; } // Updated fetch handler self.addEventListener('fetch', event => { const { request } = event; const url = new URL(request.url); // API requests - network first if (url.pathname.startsWith('/api/')) { event.respondWith(networkFirst(request, API_CACHE)); return; } // Images - cache first if (request.destination === 'image') { event.respondWith( caches.open(IMAGE_CACHE).then(cache => { return cache.match(request).then(cached => { return cached || fetch(request).then(response => { cache.put(request, response.clone()); trimCache(IMAGE_CACHE, CACHE_LIMITS[IMAGE_CACHE]); return response; }); }); }) ); return; } // Static assets - cache first if (request.destination === 'script' || request.destination === 'style' || request.destination === 'font') { event.respondWith(cacheFirst(request)); return; } // HTML pages - stale while revalidate if (request.destination === 'document') { event.respondWith( staleWhileRevalidate(request, DYNAMIC_CACHE) .catch(() => caches.match('/offline.html')) ); return; } // Default - network with cache fallback event.respondWith( fetch(request) .catch(() => caches.match(request)) ); });

تحسين الأداء

نفذ التحميل الكسول للصور وتقسيم الكود:

// Lazy loading images document.addEventListener('DOMContentLoaded', () => { const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); observer.unobserve(img); } }); }); document.querySelectorAll('img.lazy').forEach(img => { imageObserver.observe(img); }); }); // Dynamic import for analytics (code splitting) async function initAnalytics() { if (navigator.onLine) { const analytics = await import('./analytics.js'); analytics.init(); } } // Request idle callback for non-critical tasks if ('requestIdleCallback' in window) { requestIdleCallback(() => { initAnalytics(); preloadNextPage(); }); } else { setTimeout(() => { initAnalytics(); preloadNextPage(); }, 1000); }

دمج التحليلات

// analytics.js - Track PWA usage class PWAAnalytics { constructor() { this.events = []; this.sessionId = this.generateSessionId(); } generateSessionId() { return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } track(eventName, eventData = {}) { const event = { name: eventName, data: eventData, timestamp: new Date().toISOString(), sessionId: this.sessionId, online: navigator.onLine }; this.events.push(event); // Send to server if online if (navigator.onLine) { this.sendEvents(); } } async sendEvents() { if (this.events.length === 0) return; try { await fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ events: this.events }) }); this.events = []; } catch (error) { console.error('فشل إرسال التحليلات:', error); } } trackInstall() { this.track('pwa_installed', { userAgent: navigator.userAgent, platform: navigator.platform, language: navigator.language }); } trackOffline() { this.track('app_offline'); } trackOnline() { this.track('app_online'); } trackTaskAction(action, taskData) { this.track(`task_${action}`, { priority: taskData.priority, hasDescription: !!taskData.description, hasDueDate: !!taskData.dueDate }); } } // Initialize analytics const analytics = new PWAAnalytics(); // Track app lifecycle events window.addEventListener('appinstalled', () => { analytics.trackInstall(); }); window.addEventListener('online', () => { analytics.trackOnline(); analytics.sendEvents(); }); window.addEventListener('offline', () => { analytics.trackOffline(); }); export { analytics };

الاختبار باستخدام Lighthouse

قم بإجراء تدقيق Lighthouse للتحقق من امتثال PWA:

# Install Lighthouse CLI npm install -g lighthouse # Run audit lighthouse https://your-app-url.com --view # أو استخدم Chrome DevTools # 1. افتح Chrome DevTools (F12) # 2. اذهب إلى علامة التبويب "Lighthouse" # 3. حدد فئة "Progressive Web App" # 4. انقر فوق "Generate report"
قائمة التحقق من Lighthouse لـ PWA: يجب أن يحصل تطبيقك على درجة 90+ في جميع الفئات. تشمل المتطلبات الرئيسية HTTPS، وservice worker، وملف manifest، والتصميم المتجاوب، ووقت التحميل السريع، والوظائف دون اتصال.

قائمة تدقيق PWA

✓ متطلبات Progressive Web App: ✓ يتم تقديمه عبر HTTPS ✓ يسجل service worker ✓ لديه ملف manifest تطبيق الويب مع: ✓ name أو short_name ✓ icons (بما في ذلك 512x512) ✓ start_url ✓ display (standalone/fullscreen/minimal-ui) ✓ theme_color ✓ وجود viewport meta tag ✓ المحتوى بالحجم الصحيح لمنفذ العرض ✓ لديه استجابة 200 عند عدم الاتصال ✓ قابل للتثبيت ✓ وقت تحميل سريع (< 3 ثواني) ✓ متاح (تسميات ARIA، HTML دلالي) ✓ محسّن لتحسين محركات البحث (meta descriptions، titles) ✓ تحسينات الأداء: ✓ CSS/JS مصغّر ✓ صور مضغوطة ✓ تطبيق التحميل الكسول ✓ CSS مضمّن حرج ✓ تقسيم الكود ✓ تلميحات الموارد (preload، prefetch) ✓ HTTP/2 أو HTTP/3 ✓ CDN للأصول الثابتة ✓ الميزات المتقدمة: ✓ الإشعارات الفورية ✓ المزامنة في الخلفية ✓ Share target API ✓ الاختصارات ✓ معالجة الملفات ✓ Badge API

تكوين النشر

قم بتكوين الخادم الخاص بك لاستضافة PWA:

# Apache .htaccess <IfModule mod_headers.c> # Service Worker - no cache <FilesMatch "sw\.js$"> Header set Cache-Control "no-cache, no-store, must-revalidate" Header set Pragma "no-cache" Header set Expires 0 </FilesMatch> # Manifest <FilesMatch "manifest\.json$"> Header set Content-Type "application/manifest+json" Header set Cache-Control "public, max-age=604800" </FilesMatch> # Force HTTPS <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L] </IfModule> # Security headers Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "SAMEORIGIN" Header always set X-XSS-Protection "1; mode=block" Header always set Referrer-Policy "strict-origin-when-cross-origin" </IfModule> # Enable compression <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css AddOutputFilterByType DEFLATE application/javascript application/json AddOutputFilterByType DEFLATE application/manifest+json </IfModule>
# Nginx configuration server { listen 443 ssl http2; server_name your-domain.com; # SSL configuration ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; # Security headers add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; # Service Worker - no cache location ~ /sw\.js$ { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires 0; } # Manifest location ~ /manifest\.json$ { add_header Content-Type "application/manifest+json"; add_header Cache-Control "public, max-age=604800"; } # Static assets - long cache location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # Gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; gzip_min_length 1000; } # Redirect HTTP to HTTPS server { listen 80; server_name your-domain.com; return 301 https://$server_name$request_uri; }

التحسينات النهائية

// Preload critical resources <link rel="preload" href="/css/styles.css" as="style"> <link rel="preload" href="/js/app.js" as="script"> <link rel="preconnect" href="https://fonts.googleapis.com"> // Prefetch next page <link rel="prefetch" href="/settings.html"> // DNS prefetch for external resources <link rel="dns-prefetch" href="https://api.example.com"> // Critical CSS inline <style> /* Critical above-the-fold styles */ body { margin: 0; font-family: sans-serif; } .app-header { background: #2196F3; color: white; padding: 1rem; } </style> // Load full stylesheet asynchronously <link rel="stylesheet" href="/css/styles.css" media="print" onload="this.media='all'">

تتبع الأخطاء والمراقبة

// Global error handler window.addEventListener('error', (event) => { console.error('خطأ عام:', event.error); // Send error to analytics if (analytics) { analytics.track('error', { message: event.error.message, stack: event.error.stack, filename: event.filename, lineno: event.lineno, colno: event.colno }); } }); // Unhandled promise rejection window.addEventListener('unhandledrejection', (event) => { console.error('رفض غير معالج:', event.reason); if (analytics) { analytics.track('unhandled_rejection', { reason: event.reason?.toString(), promise: event.promise }); } }); // Service Worker error handling navigator.serviceWorker.addEventListener('error', (event) => { console.error('خطأ Service Worker:', event); if (analytics) { analytics.track('sw_error', { message: event.message }); } });

قائمة التحقق من النشر

قبل النشر:
  • ✓ اختبر على أجهزة حقيقية (Android، iOS، سطح المكتب)
  • ✓ تحقق من الوظائف دون اتصال
  • ✓ قم بإجراء تدقيق Lighthouse (درجة 90+)
  • ✓ اختبر تدفق التثبيت
  • ✓ تحقق من عمل الإشعارات الفورية
  • ✓ تحقق من المزامنة في الخلفية
  • ✓ اختبر على اتصال 3G بطيء
  • ✓ تحقق من فرض HTTPS
  • ✓ تحقق من وحدة التحكم للأخطاء
  • ✓ تحقق من صحة manifest.json
  • ✓ اختبر تحديثات service worker

مراقبة الإنتاج

// Track performance metrics if ('PerformanceObserver' in window) { // Track Largest Contentful Paint (LCP) const lcpObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); const lastEntry = entries[entries.length - 1]; analytics.track('performance_lcp', { value: lastEntry.renderTime || lastEntry.loadTime }); }); lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] }); // Track First Input Delay (FID) const fidObserver = new PerformanceObserver((list) => { const entries = list.getEntries(); entries.forEach(entry => { analytics.track('performance_fid', { value: entry.processingStart - entry.startTime }); }); }); fidObserver.observe({ entryTypes: ['first-input'] }); // Track Cumulative Layout Shift (CLS) let clsScore = 0; const clsObserver = new PerformanceObserver((list) => { for (const entry of list.getEntries()) { if (!entry.hadRecentInput) { clsScore += entry.value; } } }); clsObserver.observe({ entryTypes: ['layout-shift'] }); window.addEventListener('beforeunload', () => { analytics.track('performance_cls', { value: clsScore }); }); }
مخاطر شائعة: لا تخزن ملف service worker نفسه مؤقتاً. اضبط دائماً Cache-Control: no-cache لـ sw.js. قم بتحديث رقم إصدار الذاكرة المؤقتة عند نشر التغييرات. اختبر تحديثات service worker بشكل شامل.
التمرين النهائي: قم بنشر TaskMaster PWA الخاص بك على خادم إنتاج مع HTTPS. قم بإجراء تدقيق Lighthouse وحقق درجة 90+ في جميع الفئات. اختبر التطبيق على 3 أجهزة مختلفة على الأقل (سطح المكتب، Android، iOS). وثق أي مشاكل تم العثور عليها وحلولها.
تهانينا! لقد بنيت تطبيق ويب تقدمي كامل وجاهز للإنتاج مع دعم دون اتصال، والإشعارات الفورية، والمزامنة في الخلفية، واستراتيجيات التخزين المؤقت المتقدمة. تطبيقك قابل للتثبيت وسريع ويوفر تجربة شبيهة بالتطبيقات الأصلية عبر جميع المنصات.