פיצול קוד עם ייבוא דינמי ב-Next.js

איך אפשר להאיץ את האפליקציה ב-Next.js באמצעות פיצול קוד ושיטות טעינה חכמות.

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

פיצול קוד שמבוסס על מסלול ומבוסס על רכיבים

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

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

ב-Next.js יש תמיכה ב-import() דינמי, שמאפשר לייבא מודולים של JavaScript (כולל רכיבי React) באופן דינמי ולטעון כל ייבוא כמקטע נפרד. כך תוכלו לפצל את הקוד ברמת הרכיב ולשלוט בחיוב המשאבים, כדי שהמשתמשים יורידו רק את הקוד הדרוש להם לחלק באתר שהם צופים בו. ב-Next.js, הרכיבים האלה עוברים עיבוד בצד השרת (SSR) כברירת מחדל.

ייבוא דינמי בפעולה

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

בגרסה הראשונה של האפליקציה, הכלבלב גר ב-components/Puppy.js. כדי להציג את הכלב בדף, האפליקציה מייבאת את הרכיב Puppy ב-index.js באמצעות הצהרת ייבוא סטטית:

import Puppy from "../components/Puppy";

כדי לראות איך Next.js אוסף את האפליקציה, בודקים את מעקב הפעילות ברשת בכלים למפתחים:

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

  2. מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את DevTools.

  3. לוחצים על הכרטיסייה רשתות.

  4. מסמנים את התיבה Disable cache (השבתת מטמון).

  5. לטעון מחדש את הדף.

כשאתם טוענים את הדף, כל הקוד הנדרש, כולל הרכיב Puppy.js, נארז ב-index.js:

הכרטיסייה 'רשת' בכלי הפיתוח, שבה מוצגים שישה קובצי JavaScript: index.js,‏ app.js,‏ webpack.js,‏ main.js,‏ 0.js וקובץ ה-dll (ספריית קישור דינמי).

כשלוחצים על הלחצן Click me, רק הבקשה לקובץ ה-JPEG של הגור מתווספת לכרטיסייה Network:

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

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

עכשיו נבדוק גרסה שנייה של האפליקציה, שבה הייבוא הסטטי הוחלף בייבוא דינמי. ב-Next.js יש את next/dynamic, שמאפשר להשתמש בייבוא דינמי לכל הרכיבים ב-Next:

import Puppy from "../components/Puppy";
import dynamic from "next/dynamic";

// ...

const Puppy = dynamic(import("../components/Puppy"));

פועלים לפי השלבים מהדוגמה הראשונה כדי לבדוק את מעקב הרשת.

כשאתם טוענים את האפליקציה בפעם הראשונה, רק index.js מורדת. הפעם הוא קטן ב-0.5KB (הוא ירד מ-37.9KB ל-37.4KB) כי הוא לא כולל את הקוד של הרכיב Puppy:

DevTools Network שמוצגים בו אותם שישה קובצי JavaScript, מלבד index.js שקטן עכשיו ב-0.5KB.

הרכיב Puppy נמצא עכשיו במקטע נפרד, 1.js, שנטען רק כשלוחצים על הלחצן:

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

באפליקציות בעולם האמיתי הרכיבים הם בדרך כלל גדולים יותר, וטעינה מדורגת עלולה לחתוך את המטען הייעודי (payload) הראשוני של JavaScript במאות קילובייט (KB).

ייבוא דינמי עם אינדיקטור טעינה מותאם אישית

כשאתם מעמיסים משאבים באיטרציה, מומלץ לספק אינדיקטור טעינה למקרה שיחולו עיכובים. ב-Next.js אפשר לעשות זאת באמצעות הוספת ארגומנט נוסף לפונקציה dynamic():

const Puppy = dynamic(() => import("../components/Puppy"), {
  loading: () => <p>Loading...</p>
});

כדי לראות את אינדיקטור הטעינה בפעולה, אפשר לדמות חיבור איטי לרשת ב-DevTools:

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

  2. מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את DevTools.

  3. לוחצים על הכרטיסייה רשתות.

  4. מסמנים את התיבה Disable cache (השבתת המטמון).

  5. ברשימה הנפתחת Throttling בוחרים באפשרות Fast 3G.

  6. לוחצים על הלחצן Click me.

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

מסך כהה עם הטקסט

ייבוא דינמי ללא SSR

אם אתם צריכים להציג רכיב רק בצד הלקוח (לדוגמה, ווידג'ט צ'אט), תוכלו לעשות זאת על ידי הגדרת האפשרות ssr לערך false:

const Puppy = dynamic(() => import("../components/Puppy"), {
  ssr: false,
});

סיכום

התמיכה בייבוא דינמי מאפשרת ל-Next.js לבצע פיצול קוד ברמת הרכיב, וכך לצמצם את המטענים הייעודיים (payloads) של JavaScript ולקצר את זמני הטעינה של האפליקציה. כברירת מחדל, כל הרכיבים עוברים עיבוד בצד השרת, ואפשר להשבית את האפשרות הזו בכל שלב.