אופטימיזציה של טעינת משאבים

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

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

עיבוד החסימה

כפי שצוין במודול הקודם, CSS הוא משאב לחסימת עיבוד, מאחר שהוא מונע מהדפדפן לעבד תוכן עד לבנייה של מודל האובייקטים של CSS (CSSOM). הדפדפן חוסם את הרינדור כדי למנוע Flash של תוכן ללא סגנון (FOUC), שהוא מצב לא רצוי מבחינת חוויית המשתמש.

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

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

חסימת מנתח

משאב חסימת מנתח מפריע למנתח ה-HTML, למשל רכיב <script> בלי המאפיינים async או defer. כשהמנתח נתקל ברכיב <script>, הדפדפן צריך להעריך ולהפעיל את הסקריפט לפני שימשיך בניתוח שאר ה-HTML. הסיבה לכך היא שסקריפטים עשויים לשנות את ה-DOM או לגשת אליו במהלך הזמן שהוא עדיין בבנייה.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

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

סורק הטעינה מראש

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

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

  • תמונות שנטענות על ידי שירות CSS באמצעות הנכס background-image. ההפניות האלה לתמונות נמצאות ב-CSS וסורק הטעינה מראש לא יכול לגלות אותן.
  • סקריפטים שנטענים באופן דינמי בצורת תגי עיצוב של רכיב <script>, שהוחדרו ל-DOM באמצעות JavaScript או מודולים שנטענו באמצעות Dynamic import().
  • קוד HTML שעבר עיבוד בלקוח באמצעות JavaScript. תגי העיצוב האלה נמצאים במחרוזות במשאבי JavaScript, והסורק של הטעינה מראש לא יכול לראות אותם.
  • הצהרות @import של שירות CSS.

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

CSS

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

הקטנה

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

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

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

יש להסיר שירות CSS שאינו בשימוש

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

כדי לאתר קובץ CSS שלא נמצא בשימוש בדף הנוכחי, משתמשים בכלי הכיסוי בכלי הפיתוח ל-Chrome.

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

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

הימנעות מהצהרות @import של שירות CSS

אולי זה נראה לך נוח, אבל כדאי להימנע מהצהרות @import ב-CSS:

/* Don't do this: */
@import url('style.css');

בדומה לאופן שבו הרכיב <link> פועל ב-HTML, ההצהרה @import ב-CSS מאפשרת לייבא משאב CSS חיצוני מתוך גיליון סגנונות. ההבדל העיקרי בין שתי הגישות הוא שרכיב ה-HTML <link> הוא חלק מתגובת ה-HTML, ולכן התגלה הרבה יותר מהר מקובץ CSS שהורד באמצעות הצהרת @import.

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

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

ברוב המקרים, אפשר להחליף את @import באמצעות רכיב <link rel="stylesheet">. רכיבי <link> מאפשרים הורדה של גיליונות סגנונות בו-זמנית ומפחיתים את זמן הטעינה הכולל, בניגוד להצהרות @import, שמאפשרות הורדה של גיליונות סגנונות ברציפות.

הטמעת CSS חיוני

משך הזמן שחולף עד להורדת קובצי CSS עלול להגדיל את ערך ה-FCP של דף. הטבעת סגנונות קריטיים במסמך <head> מבטלת את בקשת הרשת למשאב CSS. כשהשימוש נכון, היא יכולה לקצר את זמני הטעינה הראשוניים כשאין הגדרות במטמון של הדפדפן של המשתמש. ניתן לטעון את ה-CSS הנותר באופן אסינכרוני או לצרף אותו בסוף הרכיב <body>.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

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

הדגמות של שירות CSS

JavaScript

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

JavaScript החוסם עיבוד

כשטוענים רכיבי <script> ללא המאפיינים defer או async, הדפדפן חוסם את הניתוח והעיבוד עד שהסקריפט יורד, ינותח וההרצה שלו. באופן דומה, סקריפטים מוטבעים חוסמים את המנתח עד שהסקריפט מנותח ויופעל.

async לעומת defer

async ו-defer מאפשרים לסקריפטים חיצוניים להיטען בלי לחסום את מנתח ה-HTML, ואילו סקריפטים (כולל סקריפטים מוטבעים) עם type="module" נדחים באופן אוטומטי. עם זאת, יש כמה הבדלים בין async לבין defer שחשוב להבין.

תיאור של מנגנוני טעינת סקריפטים שונים, עם פירוט של תפקידי המנתח, האחזור והביצוע על סמך מאפיינים שונים המשמשים כמו אסינכרוני, דחייה, type=&#39;module&#39; ושילוב של כל השלושה.
המקור הוא https://html.spec.whatwg.org/multipage/scripting.html

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

רינדור בצד הלקוח

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

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

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

הקטנה

בדומה ל-CSS, הקטנת JavaScript מפחיתה את גודל הקובץ של משאב סקריפט. כך אפשר להאיץ את ההורדות ולאפשר לדפדפן לעבור מהר יותר לתהליך הניתוח וההידור של JavaScript.

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

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

כאשר קוד המקור של JavaScript הקודם הוטמע, התוצאה עשויה להיראות בערך כמו קטע הקוד הבא:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

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

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

הדגמות JavaScript

בוחנים את הידע

מהי הדרך הטובה ביותר לטעון מספר קובצי CSS בדפדפן?

הצהרת @import של שירות ה-CSS.
אפשר לנסות שוב.
מספר רכיבי <link>.
נכון!

מה עושה סורק הטעינה מראש של הדפדפן?

זהו מנתח HTML משני שבוחן תגי עיצוב גולמיים כדי לגלות משאבים לפני שמנתח ה-DOM יכול לגלות אותם מוקדם יותר.
נכון!
מזהה רכיבי <link rel="preload"> במשאב HTML.
אפשר לנסות שוב.

למה הדפדפן חוסם באופן זמני את הניתוח של HTML כברירת מחדל כשמורידים משאבי JavaScript?

כדי למנוע הבזק של תוכן ללא עיצוב (FOUC).
אפשר לנסות שוב.
מכיוון שהערכת JavaScript היא משימה שכרוכה מאוד במעבד (CPU), והשהיה של ניתוח HTML נותנת יותר רוחב פס למעבד (CPU) כדי לסיים את טעינת הסקריפטים.
אפשר לנסות שוב.
מפני שסקריפטים יכולים לשנות את ה-DOM או לגשת אליו בדרך אחרת.
נכון!

השלב הבא: סיוע לדפדפן עם רמזים למשאבים

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