בניית PWA ב-Google, חלק 1

מה הצוות של Bulletin למד על עובדי שירות בזמן הפיתוח של PWA.

Douglas Parker
Douglas Parker
Joel Riley
Joel Riley
Dikla Cohen
Dikla Cohen

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

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

רקע

Bulletin היה בפיתוח פעיל מאמצע 2017 עד אמצע 2019.

למה בחרנו לפתח PWA

לפני שנתעמק בתהליך הפיתוח, נבחן למה הבנייה של PWA הייתה אטרקטיבית עבור הפרויקט הזה:

  • יכולת לבצע איטרציה במהירות. חשוב במיוחד מאחר שבתוכנית פיילוט של Bulletin בכמה שווקים.
  • Single code base (בסיס קוד יחיד). המשתמשים שלנו חולקו באופן שווה בערך בין Android ל-iOS. המשמעות של PWA אנחנו יכולים ליצור אפליקציית אינטרנט אחת שתעבוד בשתי הפלטפורמות. זה הגביר את המהירות ואת ההשפעה של הצוות.
  • מתעדכנים במהירות ולא תלויים בהתנהגות המשתמשים. אפליקציות PWA יכולות לעדכן באופן אוטומטי מפחית את כמות הלקוחות הלא מעודכנים שנמצאים בשימוש. הצלחנו לדחוף את הקצה העורפי החדש צריכים לעבור זמן קצר מאוד בשביל הלקוחות.
  • שילוב קל עם אפליקציות של צד ראשון ואפליקציות של צד שלישי. שילובים כאלה היו דרישה לאפליקציה. כשמדובר ב-PWA, בדרך כלל המשמעות הייתה רק פתיחת כתובת URL.
  • מסיר את החיכוך של התקנת אפליקציה.

המסגרת שלנו

השתמשנו ב-Polymer ל-Bulletin, אבל בכל פלטפורמה מודרנית שיש בה תמיכה טובה תפעל כמו שצריך.

מה למדנו על קובצי שירות (service worker)

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

כדאי ליצור אותו, אם אפשר

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

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

לא כל הספריות תואמות ל-service worker

ספריות JS מסוימות מניחות הנחות שלא פועלות כמצופה כשהן מופעלות על ידי Service Worker. עבור למשל, בהנחה שה-window או document זמינים, או שמשתמשים ב-API שלא זמין לשירות עובדים (XMLHttpRequest, אחסון מקומי וכו'). ודאו שכל הספריות הקריטיות הנחוצות תואמים ל-service-worker. רצינו להשתמש באפליקציית ה-PWA הספציפית הזו. gapi.js לאימות, אבל לא הייתה אפשרות לבצע את הפעולה הזו כי הוא לא תומך ב-Service Workers. על מחברי הספרייה גם לצמצם או להסיר הנחות מיותרות לגבי ההקשר של JavaScript, כשאפשר, כדי לתמוך בשימוש של Service Worker למשל על ידי הימנעות מממשקי API לא תואמים של עובדי שירות והימנעות מכניסה גלובלית .

אין גישה ל-IndexedDB במהלך האתחול

אין לקרוא IndexedDB כאשר אתחול הסקריפט של ה-Service Worker. אחרת, תוכלו להיכנס למצב הלא רצוי הבא:

  1. למשתמש יש אפליקציית אינטרנט עם גרסה N של IndexedDB (IDB)
  2. אפליקציית האינטרנט החדשה נדחפת עם IDB בגרסת N+1
  3. המשתמש נכנס לאפליקציה PWA, שמובילה להורדה של קובץ שירות (service worker) חדש
  4. קובץ שירות (service worker) חדש קורא מ-IDB לפני רישום הגורם המטפל באירועים של install, דבר שמפעיל מחזור שדרוג IDB למעבר מ-N ל-N+1
  5. למשתמש יש לקוח ישן עם גרסה N, תהליך השדרוג של Service Worker נשאר פעיל עדיין פתוחים לגרסה הישנה של מסד הנתונים
  6. Service Worker נתקע ולא מתקין

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

עמיד

למרות שסקריפטים של Service Worker פועלים ברקע, ניתן גם להפסיק אותם בכל עת, במהלך פעולות קלט/פלט (I/O) (רשת, IDB וכו'). כל תהליך ממושך צריך שניתן להמשיך אותו בכל שלב.

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

אל תסתמכו על המצב הגלובלי

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

פיתוח מקומי

שמירה מקומית של משאבים במטמון היא רכיב עיקרי ב-Service Worker. אבל במהלך הפיתוח הוא בדיוק ההיפך למה שרוצים, במיוחד כשעדכונים מתבצעים באופן מדורג. עדיין ברצונך ה-server worker התקין כדי שתוכלו לנפות באגים בבעיות הקשורות אליו או לעבוד עם ממשקי API אחרים, סנכרון ברקע או התראות. ב-Chrome אפשר לעשות זאת באמצעות כלי הפיתוח ל-Chrome הפעלת תיבת הסימון עקיפה לרשת (החלונית Application > החלונית Service Works) ב- בנוסף להפעלת תיבת הסימון השבתת המטמון בחלונית רשת כדי להשבית את מטמון הזיכרון. כדי לכסות יותר דפדפנים, בחרנו בפתרון אחר על ידי כולל סימון להשבתה של השמירה במטמון ב-Service Worker, שמופעל כברירת מחדל אצל המפתחים לבנות. כך אפשר להבטיח שהמפתחים תמיד יקבלו את השינויים האחרונים בלי בעיות בשמירה במטמון. זו חשוב לכלול גם את הכותרת Cache-Control: no-cache כדי למנוע מהדפדפן לשמור במטמון כל נכס.

Lighthouse

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

שימוש בהצגה רציפה (CD)

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

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

קבלת ערכים של קובצי cookie ב-Service Worker

לפעמים יש צורך לגשת לערכים של קובצי cookie בהקשר של Service Worker. במקרה שלנו, שנדרשים לצורך גישה לערכים של קובצי cookie כדי ליצור אסימון לאימות בקשות API מהדומיין הנוכחי. תוך שימוש Service Worker, ממשקי API סינכרוניים כמו document.cookies אינם זמינים. תמיד אפשר לשלוח הודעה ללקוחות פעילים (עם חלונות) מ-Service Worker כדי לבקש את ערכי קובצי ה-cookie ל-Service Worker יש אפשרות לפעול ברקע ללא לקוחות עם חלונות פרטיים זמין, למשל במהלך סנכרון ברקע. כדי לעקוף את הבעיה, יצרנו נקודת קצה (endpoint) שרת חזיתי שפשוט הדהה את הערך של קובץ ה-cookie בחזרה ללקוח. קובץ השירות (service worker) יצר של בקשת הרשת לנקודת הקצה הזו, וקוראים את התגובה כדי לקבל את ערכי קובצי ה-Cookie.

לאחר ההשקה של Cookie Store API, הבעיה הזו כבר לא אמורה להיות נחוצה לדפדפנים שתומכים בה, כי היא מספקת בגישה אסינכרונית לקובצי cookie של דפדפן, וניתן להשתמש בה ישירות על ידי ה-Service Worker.

מלכודות לעובדי שירות שלא נוצרו

מוודאים שהסקריפט של Service Worker משתנה אם חלו שינויים בקובץ סטטי שנשמר במטמון

דפוס PWA נפוץ הוא Service Worker שיתקין את כל קובצי האפליקציה הסטטיים במהלך שלב install, שמאפשר ללקוחות להיכנס למטמון של Cache Storage API ישירות לכל המשתמשים ביקורים נוספים . Service Works מותקנים רק כשהדפדפן מזהה שהשירות סקריפט ה-Worker השתנה בצורה מסוימת, כך שהיה עלינו לוודא שקובץ הסקריפט של ה-Service Worker עצמו השתנה בצורה מסוימת כאשר קובץ שנשמר במטמון השתנה. עשינו זאת באופן ידני על ידי הטמעת גיבוב (hash) של של קובצי המשאבים הסטטיים בסקריפט של Service Worker, כך שכל גרסה יצרה ערך בקובץ ה-JavaScript של Service Worker. ספריות של קובצי שירות, כמו כשמשתמשים בתיבת עבודה, המערכת מבצעת אוטומציה של התהליך הזה.

בדיקות יחידה (unit testing)

ממשקי Service Worker API פועלים על ידי הוספת פונקציות event listener לאובייקט הגלובלי. לדוגמה:

self.addEventListener('fetch', (evt) => evt.respondWith(fetch('/foo')));

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

import fetchHandler from './fetch_handler.js';
self.addEventListener('fetch', (evt) => evt.respondWith(fetchHandler(evt)));

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

בקרוב יופיעו חלקים 2 ו-3

בחלקים 2 ו-3 של הסדרה הזו נדון בניהול מדיה ובנושאים ספציפיים ל-iOS. אם אם ברצונך לשאול אותנו מידע נוסף על פיתוח PWA ב-Google, יש להיכנס לפרופילים של המחברים כדי לקבל הסבר איך יוצרים איתנו קשר: