בפוסט הקודם סקרתי את הצורך בPWA ומה היתרונות שהוא מגלם בתוכו.
בפוסט זה ארחיב על נושא שמירת המטמון (Caching) ואופן התנהגות הדפדפן סביב נושא שמירת המטמון.
זיהוי משאבים המצריכים שמירה למטמון
מטמון זה נושא שכמפתח web מעסיק אותי הרבה.
יש גבול לאופטימיזציה שאפשר להחיל על אתר עד שמגיעים לגבולות זמינם שלא ניתן לקצר.
כשאני ניגש לשיפור ביצועי אתר קודם כל יש את בחינת הבעיה, למשל אם אני בוחן את צד השרת אריץ profiler לראות איפה נמצא הצוואר בקבוק בסיפור.
אך כמו שכתבתי, כשמגיעים למקומות כמו קריאות צד 3 או שליפות ארוכות מהבסיס נתונים זה לא משהו שניתן לוותר עליו.
בשלב הזה מתחילים לחשוב על פתרונות מטמון, יש הרבה סוגים של פתרונות מטמון אך אפשר לחלק זאת בגסות ל2- מטמון בצד שרת ומטמון בצד לקוח.

מטמון צד לקוח
אני אתמקד במטמון צד לקוח בפוסט זה, בכל זאת PWA רץ על צד לקוח..
HTTP caching
The fastest HTTP request is the one not made
לדפדפן יש יכולת לשמור במטמון קבצים שלמים כגון תמונות או קבצי CSS ובכך לחסוך בקשות מיותרות לשרת.
הדפדפן יחשיב את הקבצים כ’טריים’ ולא ינסה להביא חדשים מהשרת ע”פ header בשם cache-control שנשלח ומתקבל מהשרת.
יש אפשרויות שונות לקבוע אסטרטגיית שמירת מטמון, בד”כ הבחירה באפשרות זו או אחרת תושפע מהפרטיות הנצרכת למסמך.
אני תהיתי לעצמי האם ניתן בעזרת מטמון זה ליצור אתר שניתן לגלוש עליו offline לאחר שגלשתי בדפים שלו ושמרתי אצלי את הכל בדפדפן, מסתבר שסוג המטמון הזה לא מותאם לדבר כזה. חלק מהדפדפנים ינסו קודם לגשת לשרת לוודא שהעותק של הHTML ששמור אצלם לא צריך רענון ואם אני אהיה בoffline אקבל שגיאה.
לקריאה נוספת ניתן לבקר באתר MDN.
AppCache
AppCache זה פיצ’ר של HTML5 שמאפשר ליצור רשימה של משאבים שאתר רוצה לשמור במטמון דפדפן של הלקוח שגולש באתר ובכך להשיג כמה מטרות, קודם כל הלקוח יחווה גלישה מהירה יותר באתר אך יותר מזה ניתן להגיע לגלישה מלאה במצב offline.
כשלקוח מגיע פעם ראשונה לאתר, הדפדפן בודק את רשימת המשאבים שצריך להכניס למטמון ומתחיל לטעון את כולם מהרשת.
זה הקובץ AppCache שלי בשם menifest.appcache:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
CACHE MANIFEST CACHE: index.html style.css css/slick.css css/slick-theme.css images/rhino-4206566_640.jpg images/lion-3576045_640.jpg images/safari-4283002_640.jpg //code.jquery.com/jquery-3.4.1.min.js NETWORK: * |
ע”מ לחבר אותו לאתר יש צורך בהוספת attribute לתגית html כך:<html lang="en" manifest="menifest.appcache">
יצרתי דף web ע”מ להדגים איך הAppCache עובד.
זו חלק מרשימת הקבצים שנטענים בדף:

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

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

אז מצד אחד זה מאכזב שאין דינוזאור לשחק איתו אבל מצד שני יש לנו דף שגם כשאין רשת עובד, שזה בהחלט מצב אידיאלי.
כל זה יפה ונחמד, אך עדיין יש לנו חסרונות בגישה הזו, אין לנו הרבה גמישות- אנחנו כבולים בדיוק לסינטקס של הקובץ ואם נרצה לדוגמה לממש design של offline first, להגיש קודם מהמטמון ואח”כ לרענן בbackground את המשאב – אין לנו אפשרות כזו.
ולמעשה, AppCache הוא deprecated, המקומות שבהם כן מומלץ להשתמש בAppCache זה בדפדפנים שלא תומכים עדיין בCache API וב Service Workers
Cache API
הcache API מתבטא באובייקט בשם caches שמוחצן לאובייקט הגלובלי window.
נכון לעכשיו יש כבר תמיכה של כל הדפדפנים הגדולים לService Workers ולאובייקט Caches.
הService Worker מתנהג כפרוקסי בין האפליקציה לבין השרת ובשביל אפשרות גלישת offline הוא נעזר באובייקט caches.
אמשיך את הדוגמה עם הדף שלי, זה הקובץ של הService Worker שמבצע את ההכנסה של המשאבים למטמון:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
var CACHE_VERSION = '2.1', CACHE_NAME = 'example_' + CACHE_VERSION, // files we want to cache filesToCache = [ '/', 'index.html', 'style.css', 'css/slick.css', 'css/slick-theme.css', 'images/rhino-4206566_640.jpg', 'images/lion-3576045_640.jpg', 'images/safari-4283002_640.jpg' ]; // if browser does not supports SW if (('serviceWorker' in navigator) === false) { throw new Error("Service Worker not supported here :("); } // register the SW file that is located in current directory navigator.serviceWorker.register('/service-worker.js', { // now the SW has access to all the project scope scope: '.' }) // Service workers make extensive use of promises .then(function(registration) { console.log('Registration successful, scope is:', registration.scope); }) .catch(function(error) { console.log('Service worker registration failed, error:', error); }); // on event of SW installation self.addEventListener('install', function(event) { console.log('Attempting to install service worker and cache static assets'); event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(filesToCache); }) ); }); // on event of resource fetch from server check // if we have it in the cache, if not - fetch it from server self.addEventListener('fetch', function(event) { event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); }); |
אפשר לראות שאני שומר את המטמון על תגית מסויימת.
באופן כללי מטמון נשמר per origin כשהכוונה של origin זה הwindow.origin, לorigin זה נשמר ללא בחירה שלנו אך התגיות שנחלק יישמרו לתוך החלק מטמון של הorigin שלנו.
השימוש של הדפדפן בorigin מתבטא בכך שבכל פעם שנגמר לדפדפן המקום במטמון הוא יתחיל למחוק מטמון origin אחר origin עד שהוא יגיע למצב תקין של מקום פנוי.
כדי שמצב כזה לא יקרה יש צורך לתחזק את המטמון ולוודא שמוחקים משאבים שכבר לא נצרכים, הזדמנות טובה זה הevent של activate כשמפעילים גרסה חדשה של SW וניתן להיפטר ממשאבים שייתכן שכבר לא נצרכים.
אביא דוגמה מהמסמכים של Google ליישום אפשרי:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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); }) ); }) ); }); |
המקום אחסון המקסימלי למטמון יכול לנוע בין 50MB ל 20GB כשהמקום אחסון מתחלק בין כל הפתרונות אחסון כמו הIndexedDB והLocalStorage ואפילו הAppCache. ניתן לקרוא על זה בהרחבה פה.
למעוניינים, ניתן למצוא את הדף דוגמה פה.