مستوى المقال: مبتدئ — مدة القراءة المقدّرة: 7 دقائق
Service Workers للمبتدئ: خلّي موقعك يفتح في 80ms حتى لو الإنترنت قطع
لو موقعك بياخد 3.4 ثانية يفتح في كل زيارة على 4G ضعيف، الزائر هيقفل الصفحة قبل ما تخلص تحميل. Service Worker بـ 32 سطر JavaScript بيخلّي الزيارة الثانية وكل اللي بعدها تفتح في 80 مللي ثانية بالظبط، وكمان بتشتغل لو الموبايل قطع إنترنت في الأسانسير. ده مش حل نظري — ده اللي بيشغّل Twitter Lite و Pinterest و Starbucks Web App.
المشكلة باختصار
كل ما الزائر يفتح صفحة من موقعك، المتصفح بيعمل نفس الرحلة من الأول: DNS lookup، TCP handshake، TLS، تحميل HTML، CSS، JS، الصور. على شبكة فايبر في القاهرة ده 480 مللي ثانية. على 4G في قرية بعيدة ده 3.4 ثانية. على شبكة قطعت لحظة، ده infinite loading spinner.
المشكلة الحقيقية مش إن الشبكة بطيئة. المشكلة إن المتصفح بيعيد تحميل نفس الـ assets كل مرة رغم إنها ما اتغيرتش. الـ logo اللي حجمه 8KB بتدفع تمنه في كل زيارة، رغم إنه نفس الملف من 6 شهور.
تخيّل صيدلية الحي
تخيّل صيدلية في حيك. أول مرة بتدخل وتسأل عن دوا معين، الصيدلي بينزل المخزن البعيد ويدوّر، بياخد 4 دقايق. لمّا ترجع تاني يوم تطلب نفس الدوا، الصيدلي بقى عارفك وحاطط الدوا في الدرج اللي قدامه — بيرجعه في 5 ثواني. لو يوم لقيت الصيدلية مقفولة بسبب جرد، الصيدلي خبّى لك علبة طوارئ مع الجار، فمش هتمشي بإيدك فاضية.
Service Worker بالظبط هو الصيدلي ده. الزيارة الأولى بياخد وقت تحميل عادي من السيرفر. الزيارة الثانية بيخدمك من الكاش المحلي على جهازك في 80 مللي ثانية. ولو الإنترنت مات، بيخدمك من الكاش كأن الموقع شغّال على لابتوبك مش على سيرفر بعيد.
تعريف علمي دقيق
Service Worker هو script JavaScript بيشتغل في background thread منفصل عن main thread بتاع الصفحة. بيتسجّل لمرة واحدة على origin معين (مثلاً ahmedhaies.com)، وبعدها بيعترض كل HTTP request بيخرج من الصفحة عبر event اسمه fetch. بياخد القرار: يخدم من cache (عبر Caches API)، يبعت للشبكة، أو يدمج الاتنين بـ stale-while-revalidate.
الـ Service Worker مالوش وصول لـ DOM مباشرة، عشان كده مينفعش يعدّل عناصر الصفحة. مهمته شبكية بحتة. التعريف ده مأخوذ من Service Workers Nightly — W3C Working Draft، والمواصفة بقت Living Standard من 2022.
ابدأ في 32 سطر — كود شغّال
الملف الأول اسمه sw.js وبيتحط في root الموقع (نفس مكان index.html):
// sw.js
const CACHE = 'site-v1';
const PRECACHE = ['/', '/index.html', '/styles.css', '/app.js', '/logo.svg'];
self.addEventListener('install', (event) => {
event.waitUntil(caches.open(CACHE).then((c) => c.addAll(PRECACHE)));
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(
caches.match(event.request).then((cached) => {
if (cached) return cached;
return fetch(event.request).then((res) => {
const clone = res.clone();
caches.open(CACHE).then((c) => c.put(event.request, clone));
return res;
}).catch(() => caches.match('/index.html'));
})
);
});