עובד שירות , service worker, שם צנוע לטכנולוגיה מדהימה

מחבר:
בתאריך:

service worker

Service Worker הוא שיטה חדישה להריץ קוד במנותק מדף האינטרנט. למה זה טוב? למשלוח התראות, push notification כמו אפליקציות של ניידים, להאצת הגלישה באתר, לגלישה ללא חיבור לאינטרנט, ועוד יישומים מלהיבים וחדשניים.

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

דוגמה לנוטיפקציות בדפדפן שמסתמכות על  service worker

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

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

 

איך מתחילים?

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

בקובץ Javascript של האפליקציה (לדוגמה, app.js) נכתוב את הקוד לרישום ה- service worker. בדוגמה להלן נרשום את ה-Service Worker שיושב בתוך הקובץ sw.js שממוקם בתיקיית השורש של האתר. scope מגדיר את הנתיבים שבהם הוא פעיל, ובמקרה שלנו הפעילות היא על כל האתר:

app.js

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker
        .register('/sw.js',{ scope: '/' })
        .then(function () {
            // console.log('Service worker registered!');
        })
        .catch(function(err) {
            console.log(err);
        });
    });
}

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

if ('serviceWorker' in navigator) { }

הרישום של ה-service worker נעשה באמצעות promise , ומתבצע בתגובה לאירוע load.

 

האירועים שאליהם מגיב ה-Service Worker

בדוגמה שאותה נפתח במדריך, נשתמש ב-service worker כדי להציג את תוכן האתר גם כשהוא off line, ולצורך כך, נכתוב קוד שיגיב לשלושה אירועים:

  1. install - התקנה ראשונית של ה-service worker, ואחסון נכסים דוגמת סקריפטים ותמונות בזיכרון המטמון (cache) של הדפדפן.
  2. fetch - אחרי ההתקנה, שימוש בנכסים שמאוחסנים בקאש במקום לשלוח קריאות לשרת.
  3. activate - במידה ובוצעו שינויים בקוד ה-Service Worker, הדפדפן מוציא אותם לפועל.

 

install - התקנה ראשונית

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

נתחיל בהגדרת שני קבועים בקובץ sw.js:

  • שמו של הקאש הסטטי.
  • מערך הנתיבים והקבצים שאותם אנחנו רוצים לשמור בקאש.

לדוגמה:

/sw.js

var STATIC_CACHE = 'static-v1';
var STATIC_CACHE_URLS   = [
   '/js/app.js',
   '/css/app.css',
   '/js/jquery.min.js',
   'https://fonts.googleapis.com/css?family=Roboto'
];

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

אחרי שהגדרנו את הקבועים, נוסיף את הקוד שמגיב לאירוע של התקנת ה-Service Worker:

self.addEventListener('install', function (event) {
    //  console.log('Installing Service Worker ...', event);
    event.waitUntil(
    caches.open(STATIC_CACHE_URLS)
        .then(function (cache) {
            //   console.log('Service Worker is caching the App Shell');
            return cache.addAll(STATIC_FILES);
        })
    );
});

בתוך האירוע install מתרחשים 3 דברים:

  1. המתודה waitUntil() דוחה את סיום פעולת ההתקנה עד שהפעולה של האחסון בקאש מסתיימת.
  2. המתודה caches.open() פותחת קאש ששמו כפי שהגדרנו בקבוע STATIC_CACHE.
  3. המתודה cache.addAll() קוראת לכל ה-URLים במערך שאותו רוצים לשמור, ושומרת את התגובה המוחזרת בקאש במבנה של מפתחות וערכים. ה-URLים הם מפתחות והתגובות הם הערכים.

 

fetch - קריאת המידע מהקאשים

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

בואו ננסה לכתוב גרסה פשטנית של הקוד:

/sw.js

self.addEventListener('fetch', function (event) {
    event.respondWith(
        caches.match(event.request)
            .then(function(response) {
            // The resource is stored in the cache, let's return it from the browser
            if (response) {
                return response;
            }
            // The resource is not stored in the cache, let's fetch it from the server
            return fetch(event.request);
         }
        )
    );
});
  • אנחנו מאזינים לאירוע fetch שתפקידו לקרוא למשאבים דוגמת סקריפטים של javascript, קבצי css, תמונות, קבצי html, נתיבים של דפים וכיו"ב , ומגיבים לו באמצעות event.respondWith()
  • אנחנו משתמשים במתודה caches.match(event.request) כדי לבדוק האם ה-URL שקוראים לו קיים בקאש.
  • במידה ונמצאת התאמה, התגובה מוחזרת מהקאש של הדפדפן. במידה ולא, התגובה מוחזרת מהשרת במקום מהדפדפן.

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

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

var CACHE_DYNAMIC_NAME = 'dynamic-v1';

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
         // The resource is stored in the cache, let's return it from the browser
        if (response) {
          return response;
        }

        // We have to clone the request so we can use it for caching and for fetching
        var fetchRequest = event.request.clone();

        return fetch(fetchRequest).then(
          function(response) {
            // Validate that the response is valid
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }

             // We need to clone the response
             var responseToCache = response.clone();

              caches.open(CACHE_DYNAMIC_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });

            return response;
          }
        );
      })
    );
});
  • נשכפל את הקריאה לשרת באמצעות event.request.clone() כי הקריאה לשרת (request) היא stream, ולכן ניתן לצרוך אותה רק פעם אחת. מה שמאפשר לנו לעשות אחת משתי פעולות. להחזיר תגובה לדפדפן או לאחסן בקאש. ומכיוון שאנחנו רוצים לעשות את שני הדברים נשכפל את ה-request, ונשמור במשתנה fetchRequest.
  • במידה והקריאה אינה תקינה (אינה קיימת או סטטוס קוד שאינו 200 או שהיא לא שייכת לסוג basic, ולכן היא מיועדת לשלוף מידע שאינו בדומיין) נחזיר את הקריאות לדפדפן בלי לשמור בקאש.
  • במידה והכול בסדר, שוב נשכפל את ה-response, ונשמור עותק אחד בקאש הדינמי, ואת העותק השני נחזיר לדפדפן.
  • השמירה נעשית באמצעות המתודה put(), שמקבלת גם את הקריאה (request) וגם את התגובה (response), ושומרת בקאש. זה בניגוד למתודה addAll() שבה השתמשנו בשלב ה-install ששולחת קריאה לשרת ושומרת את התגובה.

 

מה קורה כשאנחנו משנים את קוד ה-Service Worker?

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

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

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

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

self.addEventListener('activate', function(event) {
  // console.log('Activating the Service Worker ', event);
  event.waitUntil(
    caches.keys()
      .then(function(keyList) {
        return Promise.all(keyList.map(function(key) {
          if (key !== STATIC_CACHE && key !== DYNAMIC_CACHE) {
            // console.log('Removing old cache.', key);
            return caches.delete(key);
          }
        }));
      })
  );
  return self.clients.claim();
});

המתודה waitUntil() משהה את סיום האירוע activate עד שכל הקוד מסיים לרוץ.

הקאשים מורכבים מאובייקט שמכיל -keys ו-values. כשה-keys הם ה-URLים של הנכסים, וה-values הם הנכסים עצמם. נעבור על ה-keys של הקאשים אחד אחד באמצעות caches.keys(), ונמחק את כולם באמצעות delete().

 

תמיכה בדפדפנים השונים

הנושא של service worker הוא חדש למדי, ועם זאת זוכה לתמיכה רחבה מצד כל הדפדפנים. כדי לגלות האם התכונה זמינה בדפדפנים השונים ניתן לגלוש לכתובת https://jakearchibald.github.io/isserviceworkerready.

מדריכי JavaScript למתקדמים

הוסף תגובה חדשה

 

= 6 + 4