دليل شامل لتنفيذ عوامل الخدمة (service workers) لتطبيقات الويب التقدمية (PWAs). تعلم كيفية التخزين المؤقت للأصول، وتمكين العمل دون اتصال بالإنترنت، وتحسين تجربة المستخدم عالميًا.
تطبيقات الويب التقدمية للواجهة الأمامية: إتقان تنفيذ عامل الخدمة (Service Worker)
تمثل تطبيقات الويب التقدمية (PWAs) تطورًا كبيرًا في تطوير الويب، حيث تسد الفجوة بين مواقع الويب التقليدية وتطبيقات الهاتف المحمول الأصلية. إحدى التقنيات الأساسية التي تدعم تطبيقات الويب التقدمية هي عامل الخدمة (Service Worker). يقدم هذا الدليل نظرة عامة شاملة على تنفيذ عامل الخدمة، ويغطي المفاهيم الأساسية، والأمثلة العملية، وأفضل الممارسات لبناء تطبيقات ويب تقدمية قوية وجذابة لجمهور عالمي.
ما هو عامل الخدمة (Service Worker)؟
عامل الخدمة هو ملف JavaScript يعمل في الخلفية، بشكل منفصل عن صفحة الويب الخاصة بك. يعمل كوكيل شبكة قابل للبرمجة، حيث يعترض طلبات الشبكة ويسمح لك بالتحكم في كيفية تعامل تطبيق الويب التقدمي الخاص بك معها. وهذا يتيح ميزات مثل:
- العمل دون اتصال بالإنترنت: السماح للمستخدمين بالوصول إلى المحتوى واستخدام تطبيقك حتى عندما يكونون غير متصلين بالإنترنت.
- التخزين المؤقت: تخزين الأصول (HTML، CSS، JavaScript، الصور) لتحسين أوقات التحميل.
- الإشعارات الفورية: إرسال تحديثات في الوقت المناسب والتفاعل مع المستخدمين حتى عندما لا يستخدمون تطبيقك بشكل نشط.
- المزامنة في الخلفية: تأجيل المهام حتى يكون لدى المستخدم اتصال مستقر بالإنترنت.
تُعد عوامل الخدمة عنصرًا حاسمًا في إنشاء تجربة شبيهة بالتطبيقات على الويب، مما يجعل تطبيق الويب التقدمي الخاص بك أكثر موثوقية وجاذبية وأداءً.
دورة حياة عامل الخدمة
فهم دورة حياة عامل الخدمة أمر ضروري للتنفيذ السليم. تتكون دورة الحياة من عدة مراحل:
- التسجيل (Registration): يقوم المتصفح بتسجيل عامل الخدمة لنطاق معين (عناوين URL التي يتحكم فيها).
- التثبيت (Installation): يتم تثبيت عامل الخدمة. هنا تقوم عادةً بتخزين الأصول الأساسية مؤقتًا.
- التنشيط (Activation): يصبح عامل الخدمة نشطًا ويبدأ في التحكم في طلبات الشبكة.
- الخمول (Idle): يعمل عامل الخدمة في الخلفية، في انتظار الأحداث.
- التحديث (Update): يتم اكتشاف إصدار جديد من عامل الخدمة، مما يؤدي إلى بدء عملية التحديث.
- الإنهاء (Termination): يتم إنهاء عامل الخدمة بواسطة المتصفح للحفاظ على الموارد.
تنفيذ عامل الخدمة: دليل خطوة بخطوة
1. تسجيل عامل الخدمة
الخطوة الأولى هي تسجيل عامل الخدمة في ملف JavaScript الرئيسي الخاص بك (على سبيل المثال، `app.js`).
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(error => {
console.error('Service Worker registration failed:', error);
});
}
يتحقق هذا الكود مما إذا كانت واجهة برمجة التطبيقات `serviceWorker` مدعومة من قبل المتصفح. إذا كانت كذلك، فإنه يسجل ملف `service-worker.js`. من المهم معالجة الأخطاء المحتملة أثناء التسجيل لتوفير بديل سلس للمتصفحات التي لا تدعم عوامل الخدمة.
2. إنشاء ملف عامل الخدمة (service-worker.js)
هنا يكمن المنطق الأساسي لعامل الخدمة الخاص بك. لنبدأ بمرحلة التثبيت.
التثبيت
خلال مرحلة التثبيت، ستقوم عادةً بتخزين الأصول الأساسية اللازمة لكي يعمل تطبيق الويب التقدمي الخاص بك دون اتصال بالإنترنت. وهذا يشمل HTML و CSS و JavaScript وربما الصور والخطوط.
const CACHE_NAME = 'my-pwa-cache-v1';
const urlsToCache = [
'/',
'/index.html',
'/style.css',
'/app.js',
'/images/logo.png',
'/manifest.json'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
يحدد هذا الكود اسمًا لذاكرة التخزين المؤقت (`CACHE_NAME`) ومجموعة من عناوين URL المراد تخزينها مؤقتًا (`urlsToCache`). يتم تشغيل مستمع الحدث `install` عند تثبيت عامل الخدمة. تضمن الطريقة `event.waitUntil()` اكتمال عملية التثبيت قبل أن يصبح عامل الخدمة نشطًا. في الداخل، نفتح ذاكرة تخزين مؤقت بالاسم المحدد ونضيف جميع عناوين URL إليها. فكر في إضافة إصدارات إلى اسم ذاكرة التخزين المؤقت (`my-pwa-cache-v1`) لإبطالها بسهولة عند تحديث تطبيقك.
التنشيط
مرحلة التنشيط هي عندما يصبح عامل الخدمة الخاص بك نشطًا ويبدأ في التحكم في طلبات الشبكة. من الممارسات الجيدة مسح أي ذاكرات تخزين مؤقت قديمة خلال هذه المرحلة.
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
يحصل هذا الكود على قائمة بجميع أسماء ذاكرة التخزين المؤقت ويحذف أي ذاكرة تخزين مؤقت غير موجودة في `cacheWhitelist`. وهذا يضمن أن تطبيق الويب التقدمي الخاص بك يستخدم دائمًا أحدث إصدار من أصولك.
جلب الموارد
يتم تشغيل مستمع الحدث `fetch` في كل مرة يقوم فيها المتصفح بطلب شبكة. هنا يمكنك اعتراض الطلب وتقديم المحتوى المخزن مؤقتًا، أو جلب المورد من الشبكة إذا لم يكن مخزنًا مؤقتًا.
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
// Cache hit - return response
if (response) {
return response;
}
// Not in cache - fetch and add to cache
return fetch(event.request).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two independent copies.
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
يتحقق هذا الكود أولاً مما إذا كان المورد المطلوب موجودًا في ذاكرة التخزين المؤقت. إذا كان موجودًا، فإنه يعيد الاستجابة المخزنة مؤقتًا. إذا لم يكن كذلك، فإنه يجلب المورد من الشبكة. إذا نجح طلب الشبكة، فإنه ينسخ الاستجابة ويضيفها إلى ذاكرة التخزين المؤقت قبل إعادتها إلى المتصفح. تُعرف هذه الاستراتيجية باسم التخزين المؤقت أولاً، ثم الشبكة.
استراتيجيات التخزين المؤقت
تعتبر استراتيجيات التخزين المؤقت المختلفة مناسبة لأنواع مختلفة من الموارد. فيما يلي بعض الاستراتيجيات الشائعة:
- التخزين المؤقت أولاً، ثم الشبكة: يتحقق عامل الخدمة أولاً مما إذا كان المورد موجودًا في ذاكرة التخزين المؤقت. إذا كان موجودًا، فإنه يعيد الاستجابة المخزنة مؤقتًا. إذا لم يكن كذلك، فإنه يجلب المورد من الشبكة ويضيفه إلى ذاكرة التخزين المؤقت. هذه استراتيجية جيدة للأصول الثابتة مثل HTML و CSS و JavaScript.
- الشبكة أولاً، ثم التخزين المؤقت: يحاول عامل الخدمة أولاً جلب المورد من الشبكة. إذا نجح طلب الشبكة، فإنه يعيد استجابة الشبكة ويضيفها إلى ذاكرة التخزين المؤقت. إذا فشل طلب الشبكة (على سبيل المثال، بسبب وضع عدم الاتصال)، فإنه يعيد الاستجابة المخزنة مؤقتًا. هذه استراتيجية جيدة للمحتوى الديناميكي الذي يجب أن يكون محدثًا.
- التخزين المؤقت فقط: يعيد عامل الخدمة الموارد من ذاكرة التخزين المؤقت فقط. هذه استراتيجية جيدة للأصول التي من غير المرجح أن تتغير.
- الشبكة فقط: يجلب عامل الخدمة دائمًا الموارد من الشبكة. هذه استراتيجية جيدة للموارد التي يجب أن تكون محدثة دائمًا.
- قديم أثناء إعادة التحقق (Stale-While-Revalidate): يعيد عامل الخدمة الاستجابة المخزنة مؤقتًا على الفور ثم يجلب المورد من الشبكة في الخلفية. عندما يكتمل طلب الشبكة، فإنه يقوم بتحديث ذاكرة التخزين المؤقت بالاستجابة الجديدة. يوفر هذا تحميلًا أوليًا سريعًا ويضمن أن يرى المستخدم أحدث محتوى في النهاية.
يعتمد اختيار استراتيجية التخزين المؤقت الصحيحة على المتطلبات المحددة لتطبيق الويب التقدمي الخاص بك ونوع المورد المطلوب. ضع في اعتبارك تكرار التحديثات، وأهمية البيانات المحدثة، وخصائص الأداء المطلوبة.
التعامل مع التحديثات
عند تحديث عامل الخدمة الخاص بك، سيكتشف المتصفح التغييرات ويبدأ عملية التحديث. سيتم تثبيت عامل الخدمة الجديد في الخلفية، وسيصبح نشطًا عند إغلاق جميع علامات التبويب المفتوحة التي تستخدم عامل الخدمة القديم. يمكنك فرض التحديث عن طريق استدعاء `skipWaiting()` داخل حدث التثبيت و `clients.claim()` داخل حدث التنشيط.
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return cache.addAll(urlsToCache);
}).then(() => self.skipWaiting())
);
});
self.addEventListener('activate', event => {
const cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
}).then(() => self.clients.claim())
);
});
`skipWaiting()` تجبر عامل الخدمة المنتظر على أن يصبح عامل الخدمة النشط. `clients.claim()` تسمح لعامل الخدمة بالتحكم في جميع العملاء ضمن نطاقه، حتى أولئك الذين بدأوا بدونه.
الإشعارات الفورية
تمكّن عوامل الخدمة الإشعارات الفورية، مما يسمح لك بإعادة إشراك المستخدمين حتى عندما لا يستخدمون تطبيق الويب التقدمي الخاص بك بشكل نشط. يتطلب هذا استخدام واجهة برمجة تطبيقات الدفع (Push API) وخدمة دفع مثل Firebase Cloud Messaging (FCM).
ملاحظة: إعداد الإشعارات الفورية أكثر تعقيدًا ويتطلب مكونات من جانب الخادم. يقدم هذا القسم نظرة عامة عالية المستوى.
- اشتراك المستخدم: اطلب الإذن من المستخدم لإرسال الإشعارات الفورية. إذا تم منح الإذن، احصل على اشتراك دفع من المتصفح.
- إرسال الاشتراك إلى الخادم الخاص بك: أرسل اشتراك الدفع إلى الخادم الخاص بك. يحتوي هذا الاشتراك على المعلومات اللازمة لإرسال رسائل الدفع إلى متصفح المستخدم.
- إرسال رسائل الدفع: استخدم خدمة دفع مثل FCM لإرسال رسائل الدفع إلى متصفح المستخدم باستخدام اشتراك الدفع.
- التعامل مع رسائل الدفع في عامل الخدمة: في عامل الخدمة الخاص بك، استمع إلى حدث `push` واعرض إشعارًا للمستخدم.
إليك مثال مبسط للتعامل مع حدث `push` في عامل الخدمة الخاص بك:
self.addEventListener('push', event => {
const data = event.data.json();
const options = {
body: data.body,
icon: '/images/icon.png'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
المزامنة في الخلفية
تسمح المزامنة في الخلفية (Background Sync) بتأجيل المهام حتى يكون لدى المستخدم اتصال إنترنت مستقر. هذا مفيد لسيناريوهات مثل إرسال النماذج أو تحميل الملفات عندما يكون المستخدم غير متصل بالإنترنت.
- التسجيل للمزامنة في الخلفية: في ملف JavaScript الرئيسي الخاص بك، سجل للمزامنة في الخلفية باستخدام `navigator.serviceWorker.ready.then(registration => registration.sync.register('my-sync'));`
- التعامل مع حدث المزامنة في عامل الخدمة: في عامل الخدمة الخاص بك، استمع إلى حدث `sync` وقم بتنفيذ المهمة المؤجلة.
إليك مثال مبسط للتعامل مع حدث `sync` في عامل الخدمة الخاص بك:
self.addEventListener('sync', event => {
if (event.tag === 'my-sync') {
event.waitUntil(
// Perform the deferred task here
doSomething()
);
}
});
أفضل الممارسات لتنفيذ عامل الخدمة
- اجعل عامل الخدمة صغيرًا وفعالاً: يمكن لعامل الخدمة الكبير أن يبطئ تطبيق الويب التقدمي الخاص بك.
- استخدم استراتيجية تخزين مؤقت مناسبة لنوع المورد المطلوب: تتطلب الموارد المختلفة استراتيجيات تخزين مؤقت مختلفة.
- تعامل مع الأخطاء بسلاسة: قدم تجربة بديلة للمتصفحات التي لا تدعم عوامل الخدمة أو عند فشل عامل الخدمة.
- اختبر عامل الخدمة الخاص بك بدقة: استخدم أدوات المطور في المتصفح لفحص عامل الخدمة والتأكد من أنه يعمل بشكل صحيح.
- ضع في اعتبارك إمكانية الوصول العالمية: صمم تطبيق الويب التقدمي الخاص بك ليكون متاحًا للمستخدمين ذوي الإعاقة، بغض النظر عن موقعهم أو أجهزتهم.
- استخدم HTTPS: تتطلب عوامل الخدمة HTTPS لضمان الأمان.
- مراقبة الأداء: استخدم أدوات مثل Lighthouse لمراقبة أداء تطبيق الويب التقدمي الخاص بك وتحديد مجالات التحسين.
تصحيح أخطاء عوامل الخدمة
قد يكون تصحيح أخطاء عوامل الخدمة أمرًا صعبًا، ولكن أدوات المطور في المتصفح توفر العديد من الميزات لمساعدتك في استكشاف المشكلات وإصلاحها:
- علامة التبويب Application: توفر علامة التبويب Application في Chrome DevTools معلومات حول عامل الخدمة الخاص بك، بما في ذلك حالته ونطاقه وأحداثه.
- وحدة التحكم (Console): استخدم وحدة التحكم لتسجيل الرسائل من عامل الخدمة الخاص بك.
- علامة التبويب Network: تعرض علامة التبويب Network جميع طلبات الشبكة التي أجراها تطبيق الويب التقدمي الخاص بك وتشير إلى ما إذا كانت قد تم تقديمها من ذاكرة التخزين المؤقت أو الشبكة.
اعتبارات التدويل والترجمة (Internationalization and Localization)
عند بناء تطبيقات الويب التقدمية لجمهور عالمي، ضع في اعتبارك جوانب التدويل والترجمة التالية:
- دعم اللغة: استخدم السمة `lang` في HTML لتحديد لغة تطبيق الويب التقدمي الخاص بك. قدم ترجمات لجميع المحتوى النصي.
- تنسيق التاريخ والوقت: استخدم الكائن `Intl` لتنسيق التواريخ والأوقات وفقًا لإعدادات المستخدم المحلية.
- تنسيق الأرقام: استخدم الكائن `Intl` لتنسيق الأرقام وفقًا لإعدادات المستخدم المحلية.
- تنسيق العملة: استخدم الكائن `Intl` لتنسيق العملات وفقًا لإعدادات المستخدم المحلية.
- دعم من اليمين إلى اليسار (RTL): تأكد من أن تطبيق الويب التقدمي الخاص بك يدعم لغات RTL مثل العربية والعبرية.
- شبكة توصيل المحتوى (CDN): استخدم CDN لتقديم أصول تطبيق الويب التقدمي الخاص بك من خوادم موجودة في جميع أنحاء العالم، مما يحسن الأداء للمستخدمين في مناطق مختلفة.
على سبيل المثال، ضع في اعتبارك تطبيق ويب تقدمي يقدم خدمات التجارة الإلكترونية. يجب أن يتكيف تنسيق التاريخ مع موقع المستخدم. في الولايات المتحدة، من الشائع استخدام MM/DD/YYYY، بينما في أوروبا، يُفضل DD/MM/YYYY. وبالمثل، يجب أن تتكيف رموز العملات وتنسيق الأرقام وفقًا لذلك. يتوقع المستخدم في اليابان عرض الأسعار بالين الياباني بالتنسيق المناسب.
اعتبارات إمكانية الوصول (Accessibility)
تعتبر إمكانية الوصول أمرًا بالغ الأهمية لجعل تطبيق الويب التقدمي الخاص بك قابلاً للاستخدام من قبل الجميع، بما في ذلك المستخدمون ذوو الإعاقة. ضع في اعتبارك جوانب إمكانية الوصول التالية:
- HTML الدلالي: استخدم عناصر HTML الدلالية لتوفير البنية والمعنى لمحتواك.
- سمات ARIA: استخدم سمات ARIA لتعزيز إمكانية الوصول إلى تطبيق الويب التقدمي الخاص بك.
- التنقل باستخدام لوحة المفاتيح: تأكد من أن تطبيق الويب التقدمي الخاص بك قابل للتنقل بالكامل باستخدام لوحة المفاتيح.
- التوافق مع قارئات الشاشة: اختبر تطبيق الويب التقدمي الخاص بك باستخدام قارئ شاشة للتأكد من أنه متاح للمستخدمين المكفوفين أو ضعاف البصر.
- تباين الألوان: استخدم تباينًا كافيًا في الألوان بين النص وألوان الخلفية لجعل تطبيق الويب التقدمي الخاص بك قابلاً للقراءة للمستخدمين ذوي الرؤية المنخفضة.
على سبيل المثال، تأكد من أن جميع العناصر التفاعلية لها تسميات ARIA مناسبة حتى يتمكن مستخدمو قارئات الشاشة من فهم الغرض منها. يجب أن يكون التنقل باستخدام لوحة المفاتيح بديهيًا، مع ترتيب تركيز واضح. يجب أن يكون للنص تباين كافٍ مع الخلفية لاستيعاب المستخدمين ذوي الإعاقات البصرية.
الخاتمة
تُعد عوامل الخدمة أداة قوية لبناء تطبيقات ويب تقدمية قوية وجذابة. من خلال فهم دورة حياة عامل الخدمة، وتنفيذ استراتيجيات التخزين المؤقت، والتعامل مع التحديثات، يمكنك إنشاء تطبيقات ويب تقدمية توفر تجربة مستخدم سلسة، حتى في وضع عدم الاتصال. عند البناء لجمهور عالمي، تذكر أن تضع في اعتبارك التدويل والترجمة وإمكانية الوصول لضمان أن تطبيق الويب التقدمي الخاص بك قابل للاستخدام من قبل الجميع، بغض النظر عن موقعهم أو لغتهم أو قدرتهم. باتباع أفضل الممارسات الموضحة في هذا الدليل، يمكنك إتقان تنفيذ عامل الخدمة وإنشاء تطبيقات ويب تقدمية استثنائية تلبي احتياجات قاعدة مستخدمين عالمية متنوعة.