פיצול קוד ברמת המסלול ב-Angular

שימוש בפיצול קוד ברמת המסלול כדי לשפר את ביצועי האפליקציה

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

דוגמאות הקוד מהמאמר הזה זמינות ב-GitHub. הדוגמה לניתוב מיידי זמינה בהענף eager. הדוגמה לפיצול קוד ברמת המסלול נמצאת בהסתעפות העצלנית.

למה חשוב לפצל קוד

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

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

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

שיטות לפיצול קוד

אפשר לפצל את הקוד בשתי רמות: רמת הרכיב ורמת המסלול.

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

הפוסט הזה מתמקד בהגדרת חלוקה ברמת המסלול ב-Angular.

אפליקציה לדוגמה

לפני שנרחיב על השימוש בפיצול קוד ברמת המסלול ב-Angular, נבחן אפליקציה לדוגמה:

בודקים את ההטמעה של המודולים של האפליקציה. בתוך AppModule מוגדרים שני מסלולים: מסלול ברירת המחדל שמשויך ל-HomeComponent ומסלול nyan שמשויך ל-NyanComponent:

@NgModule({
  ...
  imports: [
    BrowserModule,
    RouterModule.forRoot([
      {
        path: '',
        component: HomeComponent,
        pathMatch: 'full'
      },
      {
        path: 'nyan',
        component: NyanComponent
      }
    ])
  ],
  ...
})
export class AppModule {}

פיצול קוד ברמת המסלול

כדי להגדיר חלוקת קוד, צריך לבצע רפאקציה (refactoring) של המסלול המהיר nyan.

גרסה 8.1.0 של Angular CLI יכולה לעשות את כל מה שצריך באמצעות הפקודה הזו:

ng g module nyan --module app --route nyan

הפעולה הזו תיצור: - מודול ניתוב חדש בשם NyanModule - מסלול ב-AppModule בשם nyan שיטען באופן דינמי את NyanModule - מסלול ברירת מחדל ב-NyanModule - רכיב בשם NyanComponent שיומר כשהמשתמש יגיע למסלול ברירת המחדל

נבצע את השלבים האלה באופן ידני כדי שנבין טוב יותר איך להטמיע פיצול קוד באמצעות Angular!

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

כדי להשתמש בפיצול קוד ברמת המסלול ב-Angular, מגדירים את המאפיין loadChildren בהצהרת המסלול ומשלבים אותו עם ייבוא דינמי:

{
  path: 'nyan',
  loadChildren: () => import('./nyan/nyan.module').then(m => m.NyanModule)
}

יש שני הבדלים עיקריים מהנתיב המהיר:

  1. הגדרתם את loadChildren במקום את component. כשמשתמשים בפיצול קוד ברמת המסלול, צריך להפנות למודולים שנטענים באופן דינמי במקום לרכיבים.
  2. ב-loadChildren, אחרי שההבטחה מתקבלת, מחזירים את NyanModule במקום להפנות ל-NyanComponent.

קטע הקוד שלמעלה מציין שכאשר המשתמש מנווט אל nyan, ‏Angular צריכה לטעון באופן דינמי את nyan.module מהספרייה nyan ולעבד את הרכיב שמשויך למסלול ברירת המחדל שמוצהר במודול.

אפשר לשייך את נתיב ברירת המחדל לרכיב באמצעות ההצהרה הבאה:

import { NgModule } from '@angular/core';
import { NyanComponent } from './nyan.component';
import { RouterModule } from '@angular/router';

@NgModule({
  declarations: [NyanComponent],
  imports: [
    RouterModule.forChild([{
      path: '',
      pathMatch: 'full',
      component: NyanComponent
    }])
  ]
})
export class NyanModule {}

הקוד הזה יוצר את NyanComponent כשהמשתמש עובר אל https://example.com/nyan.

כדי לבדוק שהנתב של Angular מוריד את nyan.module באופן עצלני בסביבה המקומית:

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

  3. לוחצים על NYAN באפליקציה לדוגמה.

  4. שימו לב שקובץ nyan-nyan-module.js מופיע בכרטיסייה 'רשת'.

טעינה מדורגת של חבילות JavaScript עם פיצול קוד ברמת המסלול

אפשר לראות את הדוגמה הזו ב-GitHub.

הצגת סימן גרפי שפעולה מתבצעת

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

כדי לעשות זאת, קודם מוסיפים את התגים של האינדיקטור בתוך האלמנט router-outlet ב-app.component.html:

<router-outlet>
  <span class="loader" *ngIf="loading"></span>
</router-outlet>

לאחר מכן מוסיפים את הכיתה AppComponent כדי לטפל באירועי ניתוב. הכיתה הזו תגדיר את הדגל loading לערך true כשהיא תשמע את האירוע RouteConfigLoadStart, ותגדיר את הדגל לערך false כשהיא תשמע את האירוע RouteConfigLoadEnd.

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  loading: boolean;
  constructor(router: Router) {
    this.loading = false;
    router.events.subscribe(
      (event: RouterEvent): void => {
        if (event instanceof NavigationStart) {
          this.loading = true;
        } else if (event instanceof NavigationEnd) {
          this.loading = false;
        }
      }
    );
  }
}

בדוגמה הבאה הוספנו זמן אחזור מלאכותי של 500 אלפיות השנייה כדי שתוכלו לראות את סמל הטעינה בפעולה.

סיכום

כדי לצמצם את גודל החבילה של אפליקציות Angular, אפשר להשתמש בפיצול קוד ברמת המסלול:

  1. שימוש ב-Angular CLI ליצירת מודולים עם טעינה איטית (lazy-loaded) כדי ליצור באופן אוטומטי תבנית של מסלול שנטען באופן דינמי.
  2. אפשר להוסיף אינדיקטור לטעינה כשהמשתמש מנווט למסלול איטי כדי להראות שיש פעולה מתמשכת.