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

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

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

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

מדידה

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

  1. כדי לראות תצוגה מקדימה של האתר, מקישים על הצגת האפליקציה. לאחר מכן מקישים על מסך מלא מסך מלא.
  2. מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את DevTools.
  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 תומך בשני הטיפים האלה באמצעות שימוש בתגובות קסם במשפטי ייבוא. הסבר מפורט יותר זמין במדריך טעינה מראש של קטעי קוד קריטיים.

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

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