אסטרטגיות לשמירה במטמון של קובץ שירות (service worker)

עד עכשיו היו רק אזכורים וקטעי קוד קטנים של הממשק של Cache. כדי להשתמש ב-Service Workers ביעילות, חשוב לאמץ שיטה אחת או יותר לשמירה במטמון. כדי להשתמש בה צריך להכיר את הממשק של Cache.

אסטרטגיית שמירה במטמון היא אינטראקציה בין אירוע fetch של קובץ שירות (service worker) לבין הממשק Cache. אופן הכתיבה של אסטרטגיית השמירה במטמון תלוי, לדוגמה, ייתכן שעדיף לטפל בבקשות לנכסים סטטיים באופן שונה ממסמכים, וזה משפיע על אופן היצירה של אסטרטגיית השמירה במטמון.

לפני שניכנס לאסטרטגיות עצמן, בוא נדבר על הממשק של Cache ומהו הממשק, וסקירה קצרה על כמה מהשיטות שהוא מציע לניהול מטמון של קובצי שירות (service worker).

הממשק Cache לעומת מטמון ה-HTTP

אם לא עבדתם בעבר עם הממשק של Cache, אולי תתפתו לחשוב שהוא זהה למטמון ה-HTTP או לפחות קשור אליו. זה לא נכון.

  • הממשק של Cache הוא מנגנון לשמירה במטמון, שנפרד לחלוטין ממטמון ה-HTTP.
  • לכל הגדרת Cache-Control שבה אתם משתמשים כדי להשפיע על מטמון ה-HTTP אין השפעה על הנכסים שמאוחסנים בממשק של Cache.

מומלץ להתייחס למטמון של הדפדפן כשכבת-על. מטמון ה-HTTP הוא מטמון ברמה נמוכה שמופעל על ידי צמדי מפתח/ערך עם הוראות מנוסחות בכותרות HTTP.

לעומת זאת, הממשק Cache הוא מטמון ברמה גבוהה שמונע על ידי JavaScript API. השיטה הזו מציעה יותר גמישות מאשר כשמשתמשים בצמדי מפתח/ערך של HTTP שהם פשוטים יחסית, והם מאפשרים אסטרטגיות של שמירה במטמון. הנה כמה שיטות API חשובות בנוגע למטמון של קובצי שירות (service worker):

  • CacheStorage.open כדי ליצור מכונה חדשה של Cache.
  • Cache.add ו-Cache.put כדי לאחסן את תגובות הרשת במטמון של קובץ השירות (service worker).
  • Cache.match כדי לאתר תגובה שנשמרה במטמון במכונה של Cache.
  • Cache.delete כדי להסיר תגובה שנשמרה במטמון ממכונה של Cache.

ועוד תחומים רבים. יש עוד שיטות שימושיות, אבל אלה השיטות הבסיסיות שתראו בהמשך המדריך.

האירוע הצנוע של fetch

החלק השני של אסטרטגיית השמירה במטמון הוא האירוע fetch של קובץ השירות (service worker). עד עכשיו שמעתם במאמר הזה על "יירוט בקשות רשת", והאירוע fetch בתוך קובץ שירות (service worker) מתרחש:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('install', (event) => {
  event.waitUntil(caches.open(cacheName));
});

self.addEventListener('fetch', async (event) => {
  // Is this a request for an image?
  if (event.request.destination === 'image') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Respond with the image from the cache or from the network
      return cache.match(event.request).then((cachedResponse) => {
        return cachedResponse || fetch(event.request.url).then((fetchedResponse) => {
          // Add the network response to the cache for future visits.
          // Note: we need to make a copy of the response to save it in
          // the cache and use the original as the request response.
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

זו דוגמה לצעצוע, ואתם יכולים לראות אותו בעצמכם, אבל היא גם מאפשרת הצצה למה שה-Service Workers יכולים לעשות. הקוד שלמעלה מבצע את הפעולות הבאות:

  1. צריך לבדוק את המאפיין destination של הבקשה כדי לראות אם זו בקשת תמונה.
  2. אם התמונה נמצאת במטמון של ה-Service Worker, יש להציג אותה משם. אם לא, יש לאחזר את התמונה מהרשת, לאחסן את התגובה במטמון ולהחזיר את תגובת הרשת.
  3. כל שאר הבקשות מועברות דרך ה-Service Worker ללא אינטראקציה עם המטמון.

האובייקט event של אחזור מכיל מאפיין request שכולל קטעי מידע שימושיים שעוזרים לזהות את הסוג של כל בקשה:

  • url – כתובת ה-URL של בקשת הרשת שמטופלת כרגע באירוע fetch.
  • method – זוהי שיטת הבקשה (למשל GET או POST).
  • mode, שמתאר את מצב הבקשה. הערך של 'navigate' משמש לעיתים קרובות כדי להבדיל בין בקשות למסמכי HTML לבין בקשות אחרות.
  • destination, שמתאר את סוג התוכן המבוקש באופן שלא נעשה שימוש בסיומת הקובץ של הנכס המבוקש.

שוב, שם המשחק הוא אסינכרוני. חשוב לזכור שהאירוע install כולל שיטה מסוג event.waitUntil שמבטיחה את המשך השימוש ב-method, וצריך להמתין לפתרון הבעיה לפני שממשיכים בהפעלה. האירוע fetch מציע שיטה דומה של event.respondWith שאפשר להשתמש בה כדי להחזיר את התוצאה של בקשה אסינכרונית של fetch או של תגובה שמוחזרת על ידי השיטה match של הממשק Cache.

אסטרטגיות לשמירה במטמון

עכשיו, אחרי שהכרתם קצת את המכונות של Cache ואת הגורם המטפל באירועים של fetch, תוכלו להתחיל לחקור אסטרטגיות לשמירה במטמון של קובצי שירות (service worker). יש כמעט אינסופיות, אבל המדריך הזה יתמקד באסטרטגיות שמתאימות ל-Workbox, כך שתוכלו לקבל מושג כללי על מה שקורה ב-Workbox.

מטמון בלבד

הצגת הזרימה מהדף, ל-Service Worker ולמטמון.

נתחיל באסטרטגיה פשוטה של שמירה במטמון, שנקרא 'מטמון בלבד'. זה רק זה: כשה-Service Worker שולט בדף, בקשות תואמות עוברות תמיד רק למטמון. כלומר, כדי שהתבנית תפעל בצורה תקינה וצריך לשמור מראש נכסים ששמורים במטמון, הנכסים האלה אף פעם לא יעודכנו במטמון עד שה-Service Worker יעודכן.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

// Assets to precache
const precachedAssets = [
  '/possum1.jpg',
  '/possum2.jpg',
  '/possum3.jpg',
  '/possum4.jpg'
];

self.addEventListener('install', (event) => {
  // Precache assets on install
  event.waitUntil(caches.open(cacheName).then((cache) => {
    return cache.addAll(precachedAssets);
  }));
});

self.addEventListener('fetch', (event) => {
  // Is this one of our precached assets?
  const url = new URL(event.request.url);
  const isPrecachedRequest = precachedAssets.includes(url.pathname);

  if (isPrecachedRequest) {
    // Grab the precached asset from the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request.url);
    }));
  } else {
    // Go to the network
    return;
  }
});

למעלה, מערך של נכסים נשמר מראש בזמן ההתקנה. כשה-Service Worker מטפל באחזורים, אנחנו בודקים אם כתובת ה-URL של הבקשה שמטופלת באירוע fetch נמצאת במערך הנכסים שנשמרו מראש. אם כן, אנחנו לוקחים את המשאב מהמטמון ומדלגים על הרשת. בקשות אחרות עוברות לרשת ורק לרשת. כדי לראות את האסטרטגיה הזו בפעולה, צפו בהדגמה הזו כשהמסוף שלכם פתוח.

רשת בלבד

הצגת הזרימה מהדף, ל-Service Worker ולרשת.

הערך ההפוך ל-'Cache Only' (מטמון בלבד) הוא 'Network Only' (רשת בלבד), שבו הבקשה מועברת דרך Service Worker לרשת בלי אינטראקציה עם המטמון של ה-Service Worker. זו אסטרטגיה טובה לשמירה על עדכניות התוכן (תגי עיצוב), אבל החיסרון הוא שהיא אף פעם לא תפעל כשהמשתמש לא מחובר לאינטרנט.

כשבקשה מועברת לרשת, לא מתקשרים ל-event.respondWith עבור בקשה תואמת. אם רוצים להשתמש מפורשות, אפשר להוסיף return; ריק ב-callback של האירוע fetch עבור בקשות שרוצים להעביר לרשת. זה מה שקורה בהדגמה של האסטרטגיה 'מטמון בלבד' לבקשות שלא נשמרות מראש.

קודם מטמון, בחזרה לרשת

מציג את הזרימה מהדף, ל-Service Worker, למטמון ואז לרשת, אם לא במטמון.

האסטרטגיה הזו היא המקום שבו הדברים קצת יותר מעורבים. כשמדובר בבקשות התאמה, התהליך מתבצע כך:

  1. הבקשה מגיעה למטמון. אם הנכס נמצא במטמון, מציגים אותו משם.
  2. אם הבקשה לא במטמון, עוברים לרשת.
  3. כשבקשת הרשת מסתיימת, מוסיפים אותה למטמון ואז מחזירים את התגובה מהרשת.

הנה דוגמה לאסטרטגיה הזו, שאותה אפשר לבדוק בהדגמה בזמן אמת:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a request for an image
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the cache first
      return cache.match(event.request.url).then((cachedResponse) => {
        // Return a cached response if we have one
        if (cachedResponse) {
          return cachedResponse;
        }

        // Otherwise, hit the network
        return fetch(event.request).then((fetchedResponse) => {
          // Add the network response to the cache for later visits
          cache.put(event.request, fetchedResponse.clone());

          // Return the network response
          return fetchedResponse;
        });
      });
    }));
  } else {
    return;
  }
});

אמנם הדוגמה הזו כוללת רק תמונות, אבל זוהי אסטרטגיה מצוינת להחיל על כל הנכסים הסטטיים (כמו CSS , JavaScript, תמונות וגופנים), במיוחד אלה שעברו גיבוב (hash). הוא משפר את המהירות של נכסים שלא ניתנים לשינוי על ידי ביצוע צדדי של בדיקות עדכניות של תוכן עם השרת שמטמון ה-HTTP עשוי להתחיל לפעול. חשוב מכך, כל הנכסים שנשמרו במטמון יהיו זמינים במצב אופליין.

קודם בחיבור לרשת, בחזרה למטמון

הצגת הזרימה מהדף, ל-Service Worker, לרשת ולמטמון אם הרשת לא זמינה.

אם תפעילו את הפקודה "מטמון קודם כל רשת שנייה", תקבלו את האסטרטגיה "הרשת הראשונה היא מטמון שנייה", וכך היא נשמעת:

  1. תחילה נכנסים לרשת כדי לקבל בקשה, ומציבים את התגובה במטמון.
  2. אם אתם לא מחוברים לאינטרנט בשלב מאוחר יותר, תוכלו לחזור לגרסה האחרונה של התגובה הזו במטמון.

השיטה הזו מתאימה במיוחד לבקשות HTML או API, כשכשאתם אונליין, אתם רוצים את הגרסה העדכנית ביותר של המשאב, אבל אתם רוצים לתת גישה אופליין לגרסה העדכנית ביותר שזמינה. כך זה עשוי להיראות כשמחילים אותו על בקשות ל-HTML:

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  // Check if this is a navigation request
  if (event.request.mode === 'navigate') {
    // Open the cache
    event.respondWith(caches.open(cacheName).then((cache) => {
      // Go to the network first
      return fetch(event.request.url).then((fetchedResponse) => {
        cache.put(event.request, fetchedResponse.clone());

        return fetchedResponse;
      }).catch(() => {
        // If the network is unavailable, get
        return cache.match(event.request.url);
      });
    }));
  } else {
    return;
  }
});

אפשר לנסות את התכונה הזו בהדגמה. תחילה עוברים לדף. ייתכן שתצטרכו לטעון מחדש כדי שתגובת ה-HTML תוצב במטמון. לאחר מכן, בכלים למפתחים, אפשר לדמות חיבור במצב אופליין ולטעון מחדש שוב. הגרסה הזמינה האחרונה תוצג באופן מיידי מהמטמון.

במצבים שבהם היכולת אופליין חשובה, אבל צריך לאזן בין היכולת הזו לבין גישה לגרסה האחרונה של מעט תגי עיצוב או נתוני API, שיטת "הרשת תחילה, שמירה במטמון" היא אסטרטגיה יציבה להשגת המטרה הזו.

לא פעיל בזמן האימות מחדש

הצגת הזרימה מהדף, ל-Service Worker, למטמון ואז מהרשת למטמון.

מבין האסטרטגיות שטיפלנו בהן עד עכשיו, "Stale-time-reVerify" היא המורכבות ביותר. זו שיטה דומה לשתי האסטרטגיות האחרונות, אבל בהליך הזה ניתנת עדיפות למהירות הגישה למשאב, תוך שמירה על העדכניות שלה ברקע. אסטרטגיה זו נראית כך:

  1. בבקשה הראשונה לנכס, צריך לאחזר אותו מהרשת, לשמור אותו במטמון ולהחזיר את תגובת הרשת.
  2. בבקשות הבאות, צריך קודם להציג את הנכס מהמטמון, אחר כך 'ברקע', לבקש ממנו שוב מהרשת ולעדכן את רשומות המטמון של הנכס.
  3. לבקשות לאחר מכן, תתקבל הגרסה האחרונה שאוחזרה מהרשת שנשמרה במטמון בשלב הקודם.

זוהי אסטרטגיה מצוינת לדברים שכן חשוב להתעדכן בהם, אבל הם לא חיוניים. חשבו על דברים כמו דמויות באתר של מדיה חברתית. הם מתעדכנים כשהמשתמשים מגיעים לעשות זאת, אבל לא חייבים להשתמש בגרסה האחרונה בכל בקשה.

// Establish a cache name
const cacheName = 'MyFancyCacheName_v1';

self.addEventListener('fetch', (event) => {
  if (event.request.destination === 'image') {
    event.respondWith(caches.open(cacheName).then((cache) => {
      return cache.match(event.request).then((cachedResponse) => {
        const fetchedResponse = fetch(event.request).then((networkResponse) => {
          cache.put(event.request, networkResponse.clone());

          return networkResponse;
        });

        return cachedResponse || fetchedResponse;
      });
    }));
  } else {
    return;
  }
});

תוכלו לראות את זה בפעולה בהדגמה נוספת בשידור חי, במיוחד אם תשימו לב לכרטיסייה'רשת' בכלים למפתחים בדפדפן, ובמציג CacheStorage של הדפדפן (אם כלי הפיתוח של הדפדפן שלכם כולל כלי כזה).

קדימה לתיבת העבודה!

זהו סיכום של בדיקת ה-API של Service Worker וממשקי API קשורים, ומשמעות הדבר היא שלמדתם מספיק איך להשתמש ישירות ב-Service Workers כדי להתחיל לטפל ב-Workbox!