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

Addy Osmani
Addy Osmani

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

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

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

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

רקע

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

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

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

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

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

שוב מהם קובצי שירות (service worker)?

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

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

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

למידע מפורט על Service Workers, כדאי לקרוא את המאמר מבוא ל-Service Workers.

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

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

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

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

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

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

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

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

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

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

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

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

ל-0.9 שניות שנדרשת כשאותו דף נטען מהמטמון של Service Worker. חיסכון של יותר מ-2 שניות בזמן ההמתנה של משתמשי הקצה שלנו נשמר.

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

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

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

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

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

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

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

למטה אפשר לראות את הגרסה המלאה שעברה רינדור ב-Chrome, ב-Firefox Nightly וב-Safari. בצד ימין מופיעה גרסת Safari שבה התוכן מעובד בשרת ללא Service Worker. בצד ימין מוצגות הגרסאות של Chrome ו-Firefox Nightly שמבוססות על Service Worker.

תמונה של Application Shell שנטענה ב-Safari, ב-Chrome וב-Firefox

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

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

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

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

תמונה של תיבת הדואר הנכנס של Google בטעינה. המחשה של תיבת הדואר הנכנס באמצעות Service Worker.

מעטפת של אפליקציות אופליין זוכה לביצועים משמעותיים, ומוכחת היטב גם באפליקציית ויקיפדיה אופליין של ג'ייק ארצ'יבלד ובאפליקציית Progressive Web App של Flipkart Lite.

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

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

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

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

תרשים הטעינה הראשונה ב-App Shell

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

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

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

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

כדי לעשות את זה:

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

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

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

מעטפת App Shell לתוכן

יישום מעשי

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

מחזור החיים של Service Worker

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

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

ביטים של שרת

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

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

תרשים הארכיטקטורה של מעטפת App Shell
  • נקודות הקצה מוגדרות לשלושה חלקים באפליקציה: כתובת ה-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 לשמירה במטמון של סביבת זמן ריצה

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

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

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

סיכום

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

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

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

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