פיצול קוד ברמת המסלול ב-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. מוסיפים אינדיקטור טעינה כשהמשתמש מנווט למסלול עצל כדי להראות שיש פעולה מתמשכת.