אפליקציות אינטרנט לטעינה מיידית עם ארכיטקטורת מעטפת של אפליקציה

אדי אוסמאני
אדי אוסמאני
מאט גאונט

מעטפת אפליקציה היא ה-HTML, ה-CSS וה-JavaScript המינימליים שמפעילים את ממשק המשתמש. מעטפת האפליקציה צריכה:

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

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

הפרדה ב-App Shell בין HTML, JS ומעטפת CSS לבין תוכן ה-HTML

רקע

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

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

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

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

תמונה של קובץ שירות (service worker) שפועל ב-DevTools עבור מעטפת האפליקציה

שוב, מהם Service Workers?

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

ל-Service Workers יש גם קבוצה מוגבלת של ממשקי API בהשוואה ל-JavaScript בהקשר רגיל של גלישה באינטרנט. זו השיטה המקובלת לעובדים באינטרנט. קובץ שירות (service worker) לא יכול לגשת ל-DOM אבל הוא יכול לגשת לפריטים כמו Cache API, והוא יכול לשלוח בקשות רשת באמצעות Fetch API. ה-IndexedDB API וה-postMessage() זמינים גם לשימוש לשמירת נתונים ולהעברת הודעות בין Service Worker לבין הדפים שהוא שולט בהם. אירועי דחיפה שנשלחים מהשרת שלכם יכולים להפעיל את Notification API כדי להגביר את מעורבות המשתמשים.

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

במבוא ל-Service Workers אפשר לקרוא פירוט נוסף על ה-Service Workers.

יתרונות ביצועים

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

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

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

בדיקה 1: בדיקה באמצעות כבל עם Nexus 5 באמצעות Chrome Dev

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

תרשים צבע של בדיקת דף אינטרנט לחיבור כבל

בדיקה 2: בדיקה ב-3G עם Nexus 5 באמצעות Chrome Dev

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

תרשים צבע לבדיקת דף אינטרנט לחיבור 3G

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

ציר זמן לתצוגה ראשונה מבדיקת דף האינטרנט

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

ציר זמן להצגת תצוגה חוזרת מבדיקת דף האינטרנט

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

האם קובצי השירות (service worker) דורשים מאיתנו לחשוב מחדש על האופן שבו אנחנו בונים אפליקציות?

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

לפיצול הזה יש השלכות נרחבות. בביקור הראשון ניתן לעבד תוכן בשרת ולהתקין את קובץ השירות (service worker) בלקוח. בביקורים הבאים צריך רק לבקש נתונים.

מה לגבי שיפור הדרגתי?

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

בהמשך ניתן לראות את הגרסה המלאה שעברה עיבוד ב-Chrome, ב-Firefox ללילה וב-Safari. בצד ימין למעלה ניתן לראות את גרסת Safari שבה התוכן מעובד בשרת ללא קובץ שירות (service worker). בצד שמאל מופיעות הגרסאות של Chrome ו-Firefox Nightly שמופעלות על ידי קובץ שירות (service worker).

תמונה של מעטפת אפליקציה שנטענה ב-Safari, ב-Chrome וב-Firefox

מתי כדאי להשתמש בארכיטקטורה הזו?

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

יש כבר אפליקציות בסביבת ייצור שמשתמשות בתבנית הזאת?

הארכיטקטורה של מעטפת האפליקציה אפשרית רק באמצעות כמה שינויים בממשק המשתמש הכולל של האפליקציה, והיא פעלה היטב באתרים גדולים כמו I/O 2015 Progressive Web App של Google ותיבת הדואר הנכנס של Google.

תמונה של תיבת הדואר הנכנס של Google נטענת. איור של תיבת הדואר הנכנס באמצעות קובץ שירות (service worker).

דוגמאות למעטפת של אפליקציות במצב אופליין מניבות ביצועים טובים מאוד, והן גם ממחישות את אפליקציית הוויקיפדיה אופליין ו-Flipkart Liteבאפליקציית האינטרנט המתקדמת של ג'ייק ארצ'יבלד.

צילומי מסך של הדגמת הוויקיפדיה של ג'ייק ארצ'יבלד

להסביר את הארכיטקטורה

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

טעינה ראשונה וטעינה של דפים אחרים

תרשים הטעינה הראשונה עם מעטפת האפליקציה

באופן כללי, הארכיטקטורה של מעטפת האפליקציה:

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

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

  • משתמשים בכלים של קובץ שירות (service worker), כמו sw-precache, לדוגמה כדי לשמור באופן מהימן את קובץ השירות (service worker) ולעדכן את ה-Service Worker שמנהל את התוכן הסטטי. (בהמשך מפורט מידע נוסף על sw-precache).

כדי להשיג זאת:

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

  • דפים יכללו סגנונות CSS מוטבעים בתג <style> בתוך המסמך <head>, כדי לספק תמונה ראשונית מהירה של מעטפת האפליקציה. כל דף יטען באופן אסינכרוני את ה-JavaScript הנחוץ לתצוגה הנוכחית. מאחר שלא ניתן לטעון CSS באופן אסינכרוני, אנחנו יכולים לבקש סגנונות באמצעות JavaScript, מכיוון שהוא אסינכרוני ולא מבוסס על ניתוח וסינכרוני. אנחנו גם יכולים לנצל את requestAnimationFrame() כדי למנוע מקרים שבהם נקבל פגיעה מהירה במטמון, ובעקבות זאת סגנונות הופכים בטעות לחלק מנתיב העיבוד הקריטי. requestAnimationFrame() מאלצת צביעה של המסגרת הראשונה לפני טעינת הסגנונות. אפשרות נוספת היא להשתמש בפרויקטים כמו loadCSS של Filament Group כדי לבקש CSS באופן אסינכרוני באמצעות JavaScript.

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

מעטפת אפליקציה לתוכן

הטמעה מעשית

כתבנו דוגמה אפקטיבית באמצעות ארכיטקטורת מעטפת האפליקציה, vanilla ES2015 JavaScript עבור הלקוח ו-Express.js עבור השרת. כמובן ששום דבר לא מונע מכם להשתמש בסטאק משלכם עבור הלקוח או החלק של השרת (למשל PHP, Ruby, Python).

מחזור החיים של קובץ שירות (service worker)

בפרויקט מעטפת האפליקציה שלנו, אנחנו משתמשים ב-sw-precache, שמציע את מחזור החיים הבא של קובץ השירות (service worker):

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

ביטים לשרת

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

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

תרשים הארכיטקטורה של מעטפת האפליקציה
  • נקודות קצה מוגדרות לשלושה חלקים באפליקציה: כתובת ה-URL המוצגת למשתמש (אינדקס/תווים כלליים לחיפוש), מעטפת האפליקציה (service worker) וחלקי ה-HTML.

  • לכל נקודת קצה יש בקר שנשלף פריסה של כידון, שבתורו יכול לשלוף חלקים ותצוגות של סרגלי האחיזה. במילים פשוטות, תצוגות חלקיות הן תצוגות שהן קטעי HTML שהועתקו לדף הסופי. הערה: לעתים קרובות קל יותר להעביר מסגרות JavaScript לארכיטקטורה של Application Shell כדי לבצע סנכרון מתקדם יותר של נתונים. לרוב, הם משתמשים בקישור ובסנכרון של נתונים במקום בחלקים.

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

  • מעטפת האפליקציה תפעל כאפליקציית אינטרנט בדף אחד, ותעשה זאת באמצעות JavaScript ו-XHR בתוכן של כתובת URL ספציפית. קריאות ה-XHR מתבצעות לנקודת קצה /partials*, שמחזירה את הקטע הקטן של HTML , CSS ו-JS שנדרשים כדי להציג את התוכן הזה. הערה: יש הרבה דרכים לגשת לכך, ו-XHR הוא רק אחת מהן. אפליקציות מסוימות ישמיעו את הנתונים שלהם (ייתכן שייעשה שימוש ב-JSON) לעיבוד ראשוני ולכן אינן "סטטיות" במובן של HTML שטוח.

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

ניהול גרסאות של קבצים

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

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

  • רשת בלבד ונכשלת אם היא במצב אופליין.

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

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

כלי עבודה

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

צילום מסך של אתר הספריות של Service Worker ב-Web Fundamentals

שימוש ב-sw-precache עבור מעטפת האפליקציה

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

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

שימוש בארגז כלים לשמירה במטמון בזמן ריצה

אפשר להשתמש ב-sw-toolbox לשמירה במטמון של זמן ריצה בשיטות שונות, בהתאם למשאב:

  • cacheFirst לתמונות, לצד מטמון ייעודי בעל שם עם מדיניות תפוגה מותאמת אישית של N maxEntries.

  • networkFirst או המהיר ביותר לבקשות API, בהתאם לעדכניות התוכן הרצויה. הכי מהיר עשוי להיות בסדר, אבל אם יש פיד API ספציפי שמתעדכן לעיתים קרובות, משתמשים ב-networkFirst.

סיכום

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

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

אם אתם כבר שוקלים להשתמש ב-Service Workers באפליקציה שלכם, בדקו את הארכיטקטורה ולבחון אם היא מתאימה לפרויקטים שלכם.

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