ספר המתכונים אופליין

Jake Archibald
Jake Archibald

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

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

מחשב המטמון – מתי לאחסן משאבים

בעזרת Service Worker אתם יכולים לטפל בבקשות בנפרד מהשמירה במטמון, אז אציג אותן בנפרד. קודם כל – שמירה במטמון – מתי צריך לבצע את הפעולה הזו?

בהתקנה — כתלויות

בהתקנה – כתלויות.
בהתקנה – כתלויות.

קובץ השירות (service worker) יוצר אירוע install. ניתן לך להשתמש בו כדי להכין דברים, דברים שחייבים להיות מוכנים לפני שתוכל לטפל באירועים אחרים. למרות שהפעולה הזו מתבצעת בכל גרסה קודמת של Service Worker עדיין פועלת ומציגה דפים, אז הפעולות שתבצעו כאן לא יפריעו לכך.

אידיאלי עבור: CSS, תמונות, גופנים, JS, תבניות... בעצם כל מה שאתה מחשיב כסטטי ל"גרסה" הזו של האתר.

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

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function (cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js',
        // etc.
      ]);
    }),
  );
});

event.waitUntil מקבלת הבטחה להגדיר את משך ההתקנה ואת הצלחתה. אם ההבטחה תידחה, ההתקנה תיחשב ככישלון וה-Service Worker הזה יופסק (אם גרסה ישנה יותר פועלת, הוא יישאר ללא שינוי). caches.open() ו-cache.addAll() מחזירים הבטחות. אם אחד מהמשאבים לא אוחזר, הקריאה ל-cache.addAll() תידחה.

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

בהתקנה – לא כתלויות

בהתקנה - לא כתלויות.
בהתקנה – לא כתלויות.

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

אידאלי: למשאבים גדולים יותר שלא צריכים מיד, כמו נכסים לשלבים מאוחרים יותר במשחק.

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function (cache) {
      cache
        .addAll
        // levels 11–20
        ();
      return cache
        .addAll
        // core assets and levels 1–10
        ();
    }),
  );
});

הדוגמה שלמעלה לא מעבירה את ההבטחה cache.addAll לרמות 11 עד 20 בחזרה ל-event.waitUntil, כך שגם אם הוא ייכשל, המשחק עדיין יהיה זמין אופליין. כמובן, תצטרכו לוודא שהרמות האלה לא קיימות, ולנסות לשמור אותן במטמון שוב.

ייתכן שה-Service Worker ייהרוג בזמן ההורדה של רמות 11 עד 20 מכיוון שהסתיים הטיפול באירועים, כלומר הם לא יישמרו במטמון. בעתיד, ה-Web Periodic Background Synchronization API יטפל במקרים כאלה ובהורדות גדולות יותר כמו סרטים. כרגע יש תמיכה ב-API הזה רק ב-Chromium fork.

בהפעלה

בהפעלה.
בזמן ההפעלה.

מתאים במיוחד: לניקוי ולהעברה.

אחרי התקנה של Service Worker חדש ואין שימוש בגרסה קודמת, האפליקציה החדשה תופעל ותקבלו אירוע activate. בגלל שהגרסה הישנה לא בעבודה, מומלץ לטפל בהעברות של סכימות ב-IndexedDB וגם למחוק מטמון שלא נמצא בשימוש.

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (cacheNames) {
      return Promise.all(
        cacheNames
          .filter(function (cacheName) {
            // Return true if you want to remove this cache,
            // but remember that caches are shared across
            // the whole origin
          })
          .map(function (cacheName) {
            return caches.delete(cacheName);
          }),
      );
    }),
  );
});

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

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

באינטראקציה של המשתמש

באינטראקציה של המשתמש.
באינטראקציה של המשתמש.

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

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

document.querySelector('.cache-article').addEventListener('click', function (event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function (cache) {
    fetch('/get-article-urls?id=' + id)
      .then(function (response) {
        // /get-article-urls returns a JSON-encoded array of
        // resource URLs that a given article depends on
        return response.json();
      })
      .then(function (urls) {
        cache.addAll(urls);
      });
  });
});

ה-API של המטמון זמין גם בדפים וגם מ-Service Workers, כלומר אפשר להוסיף למטמון ישירות מהדף.

בתגובה לרשת

בתגובה לרשת.
בתגובה לרשת.

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

אם בקשה לא תואמת לאף דבר במטמון, מקבלים אותה מהרשת, שולחים אותה לדף ומוסיפים אותה למטמון בו-זמנית.

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        return (
          response ||
          fetch(event.request).then(function (response) {
            cache.put(event.request, response.clone());
            return response;
          })
        );
      });
    }),
  );
});

כדי לאפשר שימוש יעיל בזיכרון, תוכל לקרוא את גוף התגובה/בקשה פעם אחת בלבד. בקוד שלמעלה נעשה שימוש ב-.clone() כדי ליצור עותקים נוספים שאפשר לקרוא בנפרד.

אני משתמש בזה כדי לשמור תמונות Flickr בתור מאומן לרגש.

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

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

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

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return cache.match(event.request).then(function (response) {
        var fetchPromise = fetch(event.request).then(function (networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return response || fetchPromise;
      });
    }),
  );
});

השיטה הזו דומה מאוד להגדרה stale-while-revalidate של HTTP.

בהודעה שנשלחת בדחיפה

בהודעה שנשלחת בדחיפה.
בהודעה שנשלחות מהאפליקציה.

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

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

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

הקוד הזה מעדכן את המטמון לפני שמוצג התראה:

self.addEventListener('push', function (event) {
  if (event.data.text() == 'new-email') {
    event.waitUntil(
      caches
        .open('mysite-dynamic')
        .then(function (cache) {
          return fetch('/inbox.json').then(function (response) {
            cache.put('/inbox.json', response.clone());
            return response.json();
          });
        })
        .then(function (emails) {
          registration.showNotification('New email', {
            body: 'From ' + emails[0].from.name,
            tag: 'new-email',
          });
        }),
    );
  }
});

self.addEventListener('notificationclick', function (event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});

בסנכרון ברקע

בסנכרון ברקע.
בסנכרון ברקע.

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

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

self.addEventListener('sync', function (event) {
  if (event.id == 'update-leaderboard') {
    event.waitUntil(
      caches.open('mygame-dynamic').then(function (cache) {
        return cache.add('/leaderboard.json');
      }),
    );
  }
});

שמירת עקביות במטמון

המקור מקבל כמות מסוימת של שטח פנוי כדי לבצע בו פעולות שהוא רוצה. נפח האחסון הפנוי משותף לכל נפחי האחסון המקוריים: אחסון(מקומי), IndexedDB, גישה למערכת קבצים, וכמובן מטמון.

הסכום שתקבלו לא צוין. אפשרויות האחסון משתנות בהתאם למכשיר ולתנאי האחסון. אתם יכולים לראות כמה הרווחתם:

navigator.storageQuota.queryInfo('temporary').then(function (info) {
  console.log(info.quota);
  // Result: <quota in bytes>
  console.log(info.usage);
  // Result: <used data in bytes>
});

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

כדי לעקוף את הבעיה, אפשר להשתמש בממשק StorageManager:

// From a page:
navigator.storage.persist()
.then(function(persisted) {
  if (persisted) {
    // Hurrah, your data is here to stay!
  } else {
   // So sad, your data may get chucked. Sorry.
});

כמובן, המשתמש צריך להעניק הרשאה. לשם כך, צריך להשתמש ב-Permissions API.

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

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

הצעות להצגת מודעות – תגובה לבקשות

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

מטמון בלבד

שמירה במטמון בלבד.
מטמון בלבד.

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

self.addEventListener('fetch', function (event) {
  // If a match isn't found in the cache, the response
  // will look like a connection error
  event.respondWith(caches.match(event.request));
});

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

רשת בלבד

רשת בלבד.
רשת בלבד.

אידיאלי עבור: דברים שאין להם מקבילה במצב אופליין, כמו פינגים של Analytics ובקשות מסוג GET.

self.addEventListener('fetch', function (event) {
  event.respondWith(fetch(event.request));
  // or simply don't call event.respondWith, which
  // will result in default browser behavior
});

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

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

מטמון, בחזרה לרשת.
מטמון, בחזרה לרשת.

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

כך מתקבלת התנהגות של 'מטמון בלבד' לפריטים במטמון והתנהגות של 'רשת בלבד' לכל תוכן שלא נשמר במטמון (כולל כל הבקשות שאינן GET, כי לא ניתן לשמור אותן במטמון).

מרוץ מטמון ורשתות

מרוץ מטמון ורשתות.
מטמון ומרוץ רשתות.

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

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

// Promise.race is no good to us because it rejects if
// a promise rejects before fulfilling. Let's make a proper
// race function:
function promiseAny(promises) {
  return new Promise((resolve, reject) => {
    // make sure promises are all promises
    promises = promises.map((p) => Promise.resolve(p));
    // resolve this promise as soon as one resolves
    promises.forEach((p) => p.then(resolve));
    // reject if all promises reject
    promises.reduce((a, b) => a.catch(() => b)).catch(() => reject(Error('All failed')));
  });
}

self.addEventListener('fetch', function (event) {
  event.respondWith(promiseAny([caches.match(event.request), fetch(event.request)]));
});

הרשת חוזרת למטמון

הרשת חוזרת למטמון.
הרשת חוזרת למטמון.

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

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

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

self.addEventListener('fetch', function (event) {
  event.respondWith(
    fetch(event.request).catch(function () {
      return caches.match(event.request);
    }),
  );
});

מטמון ואז רשת

מטמון ואז רשת.
מטמון ואז רשת.

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

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

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

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

הקוד בדף:

var networkDataReceived = false;

startSpinner();

// fetch fresh data
var networkUpdate = fetch('/data.json')
  .then(function (response) {
    return response.json();
  })
  .then(function (data) {
    networkDataReceived = true;
    updatePage(data);
  });

// fetch cached data
caches
  .match('/data.json')
  .then(function (response) {
    if (!response) throw Error('No data');
    return response.json();
  })
  .then(function (data) {
    // don't overwrite newer network data
    if (!networkDataReceived) {
      updatePage(data);
    }
  })
  .catch(function () {
    // we didn't get cached data, the network is our last hope:
    return networkUpdate;
  })
  .catch(showErrorMessage)
  .then(stopSpinner);

הקוד ב-Service Worker:

תמיד צריך לעבור לרשת ולעדכן את המטמון תוך כדי תנועה.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function (cache) {
      return fetch(event.request).then(function (response) {
        cache.put(event.request, response.clone());
        return response;
      });
    }),
  );
});

בתחום תרגול ריגושים, טיפלתי בנושא הזה על ידי שימוש ב-XHR במקום אחזור, וניצול לרעה של הכותרת Accept כדי לומר ל-Service Worker מאיפה לקבל את התוצאה (קוד דף, קוד של Service Worker).

חלופה גנרית

חלופה כללית.
חלופה גנרית.

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

אידאלי עבור: תמונות משניות כמו דמויות, בקשות POST שנכשלו והדף 'לא זמין במצב אופליין'.

self.addEventListener('fetch', function (event) {
  event.respondWith(
    // Try the cache
    caches
      .match(event.request)
      .then(function (response) {
        // Fall back to network
        return response || fetch(event.request);
      })
      .catch(function () {
        // If both fail, show a generic fallback:
        return caches.match('/offline.html');
        // However, in reality you'd have many different
        // fallbacks, depending on URL and headers.
        // Eg, a fallback silhouette image for avatars.
      }),
  );
});

הפריט שאליו אתם מחזירים את הגרסה עשוי להיות תלוי בהתקנה.

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

יצירת תבניות בצד של ה-Service Worker

יצירת תבניות בצד ServiceWorker.
יצירת תבניות בצד ServiceWorker.

אידאלי במקרים הבאים: דפים שלא ניתן לשמור בהם את תגובת השרת.

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

importScripts('templating-engine.js');

self.addEventListener('fetch', function (event) {
  var requestURL = new URL(event.request.url);

  event.respondWith(
    Promise.all([
      caches.match('/article-template.html').then(function (response) {
        return response.text();
      }),
      caches.match(requestURL.path + '.json').then(function (response) {
        return response.json();
      }),
    ]).then(function (responses) {
      var template = responses[0];
      var data = responses[1];

      return new Response(renderTemplate(template, data), {
        headers: {
          'Content-Type': 'text/html',
        },
      });
    }),
  );
});

סיכום של כל המידע

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

פשוט מעיינים בבקשה ומחליטים מה לעשות:

self.addEventListener('fetch', function (event) {
  // Parse the URL:
  var requestURL = new URL(event.request.url);

  // Handle requests to a particular host specifically
  if (requestURL.hostname == 'api.example.com') {
    event.respondWith(/* some combination of patterns */);
    return;
  }
  // Routing for local URLs
  if (requestURL.origin == location.origin) {
    // Handle article URLs
    if (/^\/article\//.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/\.webp$/.test(requestURL.pathname)) {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (request.method == 'POST') {
      event.respondWith(/* some other combination of patterns */);
      return;
    }
    if (/cheese/.test(requestURL.pathname)) {
      event.respondWith(
        new Response('Flagrant cheese error', {
          status: 512,
        }),
      );
      return;
    }
  }

  // A sensible default pattern
  event.respondWith(
    caches.match(event.request).then(function (response) {
      return response || fetch(event.request);
    }),
  );
});

...הבנת את זה.

זיכויים

...לסמלים החמודים:

ותודה לג'ף פוזניק על כך שזיהיתם הרבה שגיאות טיפה לפני שלחצתי על 'פרסום'.

קריאה נוספת