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

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

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

מדידה

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

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

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

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

גודל ה-bundle המקורי היה 992KB

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

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

  • לוחצים על הכרטיסייה Console.
  • מוודאים שהאפשרות 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;

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

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

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

גודל ה-Bundle ירד ל-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.

גודל ה-Bundle הוקטן ל-225KB

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

סיכום

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

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

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

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