הפחתת מטענים ייעודיים (payloads) של JavaScript באמצעות פיצול קוד

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

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

בחלון הדפדפן מוצגת אפליקציה בשם Magic Sorter עם שלושה שדות להזנת מספרים ולחצן מיון.

מדידה

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

  1. כדי לראות תצוגה מקדימה של האתר, לוחצים על View App (הצגת האפליקציה) ואז על Fullscreen מסך מלא (מסך מלא).
  2. מקישים על 'Control+Shift+J' (או על 'Command+Option+J' ב-Mac) כדי לפתוח את כלי הפיתוח.
  3. לוחצים על הכרטיסייה רשתות.
  4. מסמנים את התיבה Disable cache (השבתת המטמון).
  5. טוענים מחדש את האפליקציה.

חלונית הרשת שבה מוצג חבילה של JavaScript בגודל 71.2KB.

71.2KB של JavaScript רק כדי למיין כמה מספרים באפליקציה פשוטה. מה קורה?

בקוד המקור (src/index.js), ספריית lodash מיובאת ומשמשת באפליקציה הזו. Lodash מספק הרבה פונקציות שימושיות, אבל כאן נעשה שימוש רק בשיטה אחת מהחבילה. טעות נפוצה היא התקנה וייבוא של יחסי תלות שלמים של צד שלישי, שבהם נעשה שימוש רק בחלק קטן מהם.

אופטימיזציה

יש כמה דרכים לצמצם את גודל החבילה:

  1. כתיבת שיטת מיון מותאמת אישית במקום ייבוא ספרייה של צד שלישי
  2. שימוש בשיטה המובנית Array.prototype.sort() למיון מספרי
  3. ייבוא רק את השיטה sortBy מ-lodash ולא את הספרייה כולה
  4. כדאי להוריד את הקוד למיון רק כשהמשתמש לוחץ על הלחצן

האפשרויות 1 ו-2 הן שיטות מתאימות במיוחד להקטנת גודל החבילה (וכנראה יהיו הגיוניות ביותר באפליקציה אמיתית). אבל הם לא משמשים במדריך הזה רק לצורך ההוראה 😈.

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

מייבאים רק את מה שצריך

צריך לשנות כמה קבצים כדי לייבא רק את השיטה היחידה מ-lodash. קודם כול, מחליפים את התלות הזו ב-package.json:

"lodash": "^4.7.0",

באמצעות הקוד הבא:

"lodash.sortby": "^4.7.0",

עכשיו, ב-src/index.js, מייבאים את המודול הספציפי הזה:

import "./style.css";
import _ from "lodash";
import sortBy from "lodash.sortby";

מעדכנים את אופן המיון של הערכים:

form.addEventListener("submit", e => {
  e.preventDefault();
  const values = [input1.valueAsNumber, input2.valueAsNumber, input3.valueAsNumber];
  const sortedValues = _.sortBy(values);
  const sortedValues = sortBy(values);

  results.innerHTML = `
    <h2>
      ${sortedValues}
    </h2>
  `
});

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

חלונית הרשת שבה מוצגת חבילת JavaScript בגודל 15.2KB.

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

פיצול קוד

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

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

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

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

מתחילים בהסרת הייבוא ברמה העליונה של שיטת המיון ב-src/index.js:

import sortBy from "lodash.sortby";

וייבאו אותו בתוך ה-event listener שמופעל כשלוחצים על הלחצן:

form.addEventListener("submit", e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput())
    .catch(err => { alert(err) });
});

התכונה import() היא חלק מהצעה (שנמצאת כרגע בשלב 3 בתהליך TC39) שכוללת את היכולת לייבא מודול באופן דינמי. כבר נוספה תמיכה ב-webpack לתכונה הזו, והיא פועלת לפי אותה תחביר שצוין בהצעה.

הפונקציה import() מחזירה הבטחה, וכשהיא מתקבלת, המודול שנבחר מופיע כמקטע נפרד. אחרי שהמודול מוחזר, נעשה שימוש ב-module.default כדי להתייחס לייצוא ברירת המחדל שמסופק על ידי lodash. ההבטחה מקושרת ל-.then אחר שמפעיל שיטה sortInput כדי למיין את שלושת ערכי הקלט. בסוף שרשרת ההבטחות,catch() משמש לטיפול במקרים שבהם ההבטחה נדחית בגלל שגיאה.

השלב האחרון הוא לכתוב את השיטה sortInput בסוף הקובץ. זו צריכה להיות פונקציה שמחזירה פונקציה שמקבלת את השיטה המיובאת מ-lodash.sortBy. לאחר מכן, הפונקציה המקוננת יכולה למיין את שלושת ערכי הקלט ולעדכן את ה-DOM.

const sortInput = () => {
  return (sortBy) => {
    const values = [
      input1.valueAsNumber,
      input2.valueAsNumber,
      input3.valueAsNumber
    ];
    const sortedValues = sortBy(values);

    results.innerHTML = `
      <h2>
        ${sortedValues}
      </h2>
    `
  };
}

מעקב

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

חלונית הרשת שבה מוצג חבילה של JavaScript בגודל 2.7KB.

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

חלונית רשת שמציגה חבילת JavaScript בגודל 2.7KB ואחריה חבילת JavaScript בגודל 13.9KB.

שימו לב שהמספרים עדיין ממוינים.

סיכום

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

ממשק משתמש עם טעינה מדורגת

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

טעינה מדורגת של מודולים של צמתים של צד שלישי

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

טעינה מדורגת באמצעות מסגרת JavaScript

הרבה מסגרות וספריות פופולריות שמשתמשות ב-Webpack מספקות הפשטות שבעזרתן קל יותר לבצע טעינת נתונים בזמן אמת (lazy loading) מאשר להשתמש בייבוא דינמי באמצע האפליקציה.

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

טעינה מראש ושליפה מראש (prefetch)

כשהדבר אפשרי, השתמשו ברמזים של הדפדפן, כמו <link rel="preload"> או <link rel="prefetch">, כדי לנסות לטעון מודולים קריטיים מוקדם יותר. ה-Webpack תומך בשני הרמזים באמצעות שימוש בתגובות קסם במסמכי ייבוא. הסבר מפורט יותר זמין במדריך טעינה מראש של קטעי קוד קריטיים.

טעינה מדורגת של יותר מקוד

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