הסרת קוד שלא נמצא בשימוש

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

צילום מסך של אפליקציה

מדידה

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

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

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

  1. מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את DevTools.
  2. לוחצים על הכרטיסייה רשתות.
  3. מסמנים את התיבה Disable cache (השבתת המטמון).
  4. טוענים מחדש את האפליקציה.

גודל החבילה המקורי הוא 992KB

כדי לטעון את האפליקציה הפשוטה הזו, נשלחים כמעט 1MB של JavaScript!

בודקים את האזהרות בפרויקט ב-DevTools.

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

מסנן אזהרות

  • בודקים את האזהרה שמוצגת.

אזהרה במסוף

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

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

ניתוח החבילה

יש שתי יחסי תלות עיקריים באפליקציה:

  • Firebase: פלטפורמה שמספקת מספר שירותים שימושיים לאפליקציות ל-iOS, ל-Android או לאינטרנט. כאן מסד הנתונים בזמן אמת משמש לאחסון ולסנכרון של המידע על כל גורי החתולים בזמן אמת.
  • Moment.js: ספריית שירות שמקלה על הטיפול בתאריכים ב-JavaScript. תאריך הלידה של כל גורה מאוחסן במסד הנתונים של Firebase, והערך של moment משמש לחישוב הגיל שלו בשבועות.

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

ניתוח של ה-bundler כדי לקבל מושג טוב יותר לגבי מה שקורה. יש כמה כלים שונים שנוצרו על ידי הקהילה שיכולים לעזור לכם לעשות זאת, כמו webpack-bundle-analyzer.

החבילה של הכלי הזה כבר כלולה באפליקציה כ-devDependency.

"devDependencies": {
  //...
  "webpack-bundle-analyzer": "^2.13.1"
},

המשמעות היא שאפשר להשתמש בו ישירות בקובץ התצורה של webpack. מייבאים אותו ממש בהתחלה של webpack.config.js:

const path = require("path");

//...
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
  .BundleAnalyzerPlugin;

עכשיו מוסיפים אותו כפלאגין ממש בסוף הקובץ, בתוך המערך plugins:

module.exports = {
  //...
  plugins: [
    //...
    new BundleAnalyzerPlugin()
  ]
};

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

Webpack Bundle Analyzer

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

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

בעזרת הכלי webpack-bundle-analyzer קל יותר לזהות חבילות שלא בשימוש או לא נחוצות, שמהוות אחוז גדול מהחבילה.

הסרת חבילות שלא בשימוש

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

  • firestore
  • auth
  • storage
  • messaging
  • functions

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

כדי שהאפליקציה תופיע שוב, צריך לבטל את השינויים ב-webpack.config.js:

  • מסירים את BundleAnalyzerPlugin מרשימת הפלאגינים:
plugins: [
  //...
  new BundleAnalyzerPlugin()
];
  • עכשיו מסירים את הייבוא שלא בשימוש בחלק העליון של הקובץ:
const path = require("path");

//...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

האפליקציה אמורה להיטען כרגיל עכשיו. משנים את src/index.js כדי לעדכן את הייבוא מ-Firebase.

import firebase from 'firebase';
import firebase from 'firebase/app';
import 'firebase/database';

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

גודל החבילה צומצם ל-480KB

יותר ממחצית מגודל החבילה הוסרה. Firebase מספק שירותים רבים ומגוונים, ומאפשר למפתחים לכלול רק את השירותים הנחוצים להם בפועל. באפליקציה הזו, רק firebase/database שימש לאחסון ולסנכרון של כל הנתונים. תמיד צריך לייבא את firebase/app, שמגדיר את פלטפורמת ה-API לכל אחד מהשירותים השונים.

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

גודל החבילה הצטמצם משמעותית, אבל עדיין יש לנו עבודה לעשות. 😈

הסרת חבילות לא נחוצות

בניגוד ל-Firebase, אי אפשר לייבא חלקים מהספרייה moment בקלות, אבל אולי אפשר להסיר אותה לגמרי?

תאריך הלידה של כל גוריחת חמודה מאוחסן בפורמט Unix (אלפיות שנייה) במסד הנתונים של Firebase.

תאריכי לידה שמאוחסנים בפורמט Unix

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

כמו תמיד, חשוב לא להעתיק ולהדביק את הקוד בזמן שמבצעים את ההוראות. מתחילים בהסרת moment מהייבוא ב-src/index.js.

import firebase from 'firebase/app';
import 'firebase/database';
import * as moment from 'moment';

יש מאזין לאירועים ב-Firebase שמטפל בשינויי ערכים במסד הנתונים שלנו:

favoritesRef.on("value", (snapshot) => { ... })

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

const ageInWeeks = birthDate => {
  const WEEK_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 7;
  const diff = Math.abs((new Date).getTime() - birthDate);
  return Math.floor(diff / WEEK_IN_MILLISECONDS);
}

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

לבסוף, אפשר להסיר את כל המופעים של moment ב-event listener באמצעות הפונקציה הזו במקום זאת:

favoritesRef.on("value", (snapshot) => {
  const { kitties, favorites, names, birthDates } = snapshot.val();
  favoritesScores = favorites;

  kittiesList.innerHTML = kitties.map((kittiePic, index) => {
    const birthday = moment(birthDates[index]);

    return `
      <li>
        <img src=${kittiePic} onclick="favKittie(${index})">
        <div class="extra">
          <div class="details">
            <p class="name">${names[index]}</p>
            <p class="age">${moment().diff(birthday, 'weeks')} weeks old</p>
            <p class="age">${ageInWeeks(birthDates[index])} weeks old</p>
          </div>
          <p class="score">${favorites[index]} ❤</p>
        </div>
      </li>
    `})
});

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

גודל החבילה צומצם ל-225KB

הגודל של החבילה שלנו הצטמצם ביותר ממחצית!

סיכום

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

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

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

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