تطبيقات الويب التقدمية
بناء مشروع PWA (الجزء الثالث)
بناء مشروع 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). وثق أي مشاكل تم العثور عليها وحلولها.
تهانينا! لقد بنيت تطبيق ويب تقدمي كامل وجاهز للإنتاج مع دعم دون اتصال، والإشعارات الفورية، والمزامنة في الخلفية، واستراتيجيات التخزين المؤقت المتقدمة. تطبيقك قابل للتثبيت وسريع ويوفر تجربة شبيهة بالتطبيقات الأصلية عبر جميع المنصات.