الكاش في المتصفح مش فولدر على الهارد تفتحه من مدير الملفات. هو صناديق بأسماء جوّه Cache Storage. كل صندوق فيه طلبات وردود. الأوامر تحت دي هي اللي مثال الموقع بيستخدمها، بنفس الترتيب اللي هتحتاجه وأنت بتكتب الـ Service Worker.
١. اعمل فولدر كاش بالاسم ده
const cache = await caches.open("menu-v1");caches.open بترجّع الصندوق. الاسم نص أنت بتختاره. غيّر الاسم (menu-v2) لما شكل الحاجات المحفوظة يتغير، عشان القديمة تتشال في activate.
٢. الطلب ده موجود في الفولدر؟
const hit = await cache.match(request);if (hit) { // hit هو الـ Response المحفوظ}لو مش موجود، match بترجّع undefined مش خطأ. caches.match(request) بتدوّر في كل الصناديق. cache.match بتدوّر في الصندوق اللي فتحته بس. في مثال الموقع بنفتح صندوق الصفحات وندوّر فيه بالمسار من غير ?hl=.
٣. امسك الطلب وعدّل الرد قبل ما يتخزّن
جسم الرد بيتقرأ مرة واحدة. عشان كده بنبني Response جديد ونخزّن نسخة منه بـ clone.
self.addEventListener("fetch", (event) => { event.respondWith((async () => { const cache = await caches.open("menu-v1"); const remote = await fetch(event.request); if (!remote.ok) return remote; const headers = new Headers(remote.headers); headers.set("x-cached-at", String(Date.now())); const stamped = new Response(await remote.blob(), { status: remote.status, statusText: remote.statusText, headers, }); await cache.put(event.request, stamped.clone()); return stamped; })());});fetch هنا الحدث: كل طلب الصفحة بتعمله يعدّي على الـ SW. respondWith يقول للمتصفح “أنا هرد”. put يحط الرد في الصندوق. الموقع بيحط كمان x-sw-build عشان يعرف النسخة من أنهي deploy.
clone. الجسم بيخلص.
٤. الطلب GET ولا POST؟
if (event.request.method !== "GET") return;return من غير respondWith يعني سيّب المتصفح يكمّل للنت عادي. cache.put بيرفض أي حاجة غير GET. الـ POST فيه جسم وبيتكرر غلط لو اتخزن، فسيّبه يعدّي. نفس السطر موجود في أول fetch بتاع الموقع.
٥. امسح فولدر الكاش كله
await caches.delete("menu-v1");ده بيمسح الصندوق واسمه. مش بيمسح مدخل واحد.
٦. امسح طلب واحد من الفولدر
const gone = await cache.delete(request);gone يبقى true لو لقى الطلب ومسحه. المفتاح لازم يطابق اللي اتخزن بيه. الموقع بيخزّن الصفحات بالمسار من غير query، فالمسح يستخدم نفس المفتاح.
٧. هات الرد من الكاش مش من النت
const cached = await cache.match(request);if (cached) return cached;مفيش fetch هنا. لو الصندوق فاضي، مفيش رد وهتكمل انت بقى تجيب من النت أو ترجع صفحة offline.
٨. هات الرد من النت
const remote = await fetch(request);دي شبكة عادية. الـ SW لسه يقدر يخزّن الناتج بعدها بـ put. لو عايز النت بس ومن غير كاش، متفتحش صندوق خالص.
سطور مثال الموقع، سطر سطر
self.addEventListener("install", (event) => { event.waitUntil( caches.open(SHELL).then((cache) => cache.addAll(SHELL_FILES)), );});self.addEventListener("activate", (event) => { event.waitUntil((async () => { const keep = [SHELL, PAGES, ASSETS, STATIC]; for (const key of await caches.keys()) { if (!keep.includes(key)) await caches.delete(key); } await self.clients.claim(); })());});المتصفح بيشغّل الحدث ده لما يشوف ملف SW جديد (البايتات اتغيرت). في الوقت ده الـ SW لسه ماسكش الصفحات. بنستخدمه نحفظ الملفات الأساسية قبل ما نرد على حد.
SHELL نص زي shell- ورقم الـ build. كل deploy صندوق جديد. addAll يجيب القائمة ويحطها. لو ملف واحد فشل، التثبيت كله يفشل والنسخة القديمة تفضل شغالة.
بيحصل لما النسخة دي تبقى هي الشغالة، بعد ما القديمة تسيب المكان. ده وقت تنضيف الصناديق اللي اسمها مش في keep.
قائمة أسماء كل الصناديق على الأصل ده، مش الملفات اللي جواها. اللوب يمسح أي اسم مش في القائمة البيضا.
من غير السطر ده، الصفحات المفتوحة قبل التفعيل تفضل مع الـ SW القديم لحد ما تتعمل لها reload. claim يخلّي النسخة الجديدة تمسك التابات المفتوحة دلوقتي.
event.waitUntil يقول للمتصفح: متقفلش الـ SW لحد ما الـ promise تخلّص. من غيرها التثبيت أو التفعيل ممكن يتقطع في النص والكاش يفضل نصّه.
أوامر تستاهل تتحط معاهم
skipWaiting في آخر install عشان النسخة الجديدة متقعدش في حالة waiting. الموقع بيعمله بعد ما addAll تنجح. من غيره، المستخدم يقفل كل التابات قبل ما activate يحصل.
خلّصت الفصل لما
١) caches.delete ولا cache.delete تمسح صندوق باسمه؟
caches.delete بتمسح الصندوق كله. cache.delete بتمسح طلب واحد جواه.٢) ليه put مش بيقبل POST؟
GET. الـ POST سيّبه يعدّي للنت من غير ما تتخزن.