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

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

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

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

הממשק 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). עד עכשיו במסמך הזה שמעתם קצת על "יירוט בקשות רשת", והאירוע 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;
  }
});

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

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

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

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

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

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

עכשיו, אחרי שמכירים קצת את המכונות של 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 ולרשת.

האפשרות ההפוך ל'מטמון בלבד' הוא 'רשת בלבד', כאשר בקשה מועברת לרשת דרך 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, למטמון ואז מהרשת למטמון.

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

  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 שלו (אם כלים למפתחים בדפדפן שלך כוללים כלי כזה).

קדימה ל-Workbox!

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