שימוש בפיצול קוד ברמת המסלול כדי לשפר את ביצועי האפליקציה
בפוסט הזה נסביר איך להגדיר פיצול קוד ברמת המסלול באפליקציית 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)
}
יש שני הבדלים עיקריים מהנתיב המהיר:
- הגדרתם את
loadChildren
במקום אתcomponent
. כשמשתמשים בפיצול קוד ברמת המסלול, צריך להפנות למודולים שנטענים באופן דינמי במקום לרכיבים. - ב-
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
באופן עצלני בסביבה המקומית:
- מקישים על Control+Shift+J (או על Command+Option+J ב-Mac) כדי לפתוח את DevTools.
לוחצים על הכרטיסייה רשתות.
לוחצים על NYAN באפליקציה לדוגמה.
שימו לב שקובץ
nyan-nyan-module.js
מופיע בכרטיסייה 'רשת'.
אפשר לראות את הדוגמה הזו ב-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, אפשר להשתמש בפיצול קוד ברמת המסלול:
- שימוש ב-Angular CLI ליצירת מודולים עם טעינה איטית (lazy-loaded) כדי ליצור באופן אוטומטי תבנית של מסלול שנטען באופן דינמי.
- אפשר להוסיף אינדיקטור לטעינה כשהמשתמש מנווט למסלול איטי כדי להראות שיש פעולה מתמשכת.