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

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

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

מדידה

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

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

עכשיו אפשר ללחוץ על החתלתול האהוב! באפליקציה הזו נעשה שימוש ב-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

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

סיכום

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

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

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

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