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

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

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

מדידה

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

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

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

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

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

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

בודקים את האזהרות לגבי הפרויקט בכלי הפיתוח.

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

מסנן אזהרות

  • מעיינים באזהרה שמוצגת.

אזהרה במסוף

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

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

ניתוח החבילה

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

  • ‫Firebase: פלטפורמה שמספקת כמה שירותים שימושיים לאפליקציות ל-iOS, ל-Android ולאפליקציות אינטרנט. כאן נעשה שימוש ב-Realtime Database כדי לאחסן ולסנכרן את המידע על כל חתלתול בזמן אמת.
  • ‫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

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

גודל הנתון הסטטיסטי הגודל לפני הקטנה או דחיסה.
גודל מנותח גודל החבילה בפועל בתוך ה-bundle אחרי שהיא עברה קומפילציה. גרסה 4 של webpack (שמשמשת באפליקציה הזו) מקטינה את הקבצים המקומפלים באופן אוטומטי, ולכן הגודל הזה קטן יותר מהגודל הסטטיסטי.
גודל הקובץ אחרי דחיסה ב-Gzip גודל החבילה אחרי דחיסה בקידוד 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;

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

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

עכשיו, כשהאפליקציה נטענת מחדש, האזהרה של כלי הפיתוח לא מוצגת. כשפותחים את החלונית 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';

יש רכיב event listener של 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>
    `})
});

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

גודל החבילה הוקטן ל-225KB

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

סיכום

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

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

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

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