Suddivisione del codice a livello di route in Angular

Migliora le prestazioni della tua app utilizzando la suddivisione del codice a livello di route.

Questo post spiega come configurare la suddivisione del codice a livello di route in un'applicazione Angular, che può ridurre le dimensioni del bundle JavaScript e migliorare notevolmente il Time to Interactive.

Puoi trovare gli esempi di codice di questo articolo su GitHub. L'esempio di routing eager è disponibile nel branch eager. L'esempio di suddivisione del codice a livello di route si trova nel ramo lazy.

Perché la suddivisione del codice è importante

La complessità sempre maggiore delle applicazioni web ha portato a un aumento significativo della quantità di JavaScript inviata agli utenti. I file JavaScript di grandi dimensioni possono ritardare notevolmente l'interattività, quindi possono essere una risorsa costosa, soprattutto sui dispositivi mobili.

Il modo più efficiente per ridurre i bundle JavaScript senza sacrificare le funzionalità delle applicazioni è introdurre una suddivisione aggressiva del codice.

La suddivisione del codice consente di dividere il codice JavaScript dell'applicazione in più blocchi associati a route o funzionalità diverse. Questo approccio invia agli utenti solo il codice JavaScript di cui hanno bisogno durante il caricamento iniziale dell'applicazione, mantenendo bassi i tempi di caricamento.

Tecniche di suddivisione del codice

La suddivisione del codice può essere eseguita a due livelli: a livello di componente e a livello di route.

  • Nella suddivisione del codice a livello di componente, i componenti vengono spostati nei propri blocchi JavaScript e caricati in modalità lazy quando sono necessari.
  • Nella suddivisione del codice a livello di route, incapsuli la funzionalità di ogni route in un chunk separato. Quando gli utenti navigano nell'applicazione, recuperano i chunk associati ai singoli percorsi e ottengono la funzionalità associata quando ne hanno bisogno.

Questo post è incentrato sulla configurazione della suddivisione a livello di route in Angular.

Applicazione di esempio

Prima di approfondire l'utilizzo della suddivisione del codice a livello di route in Angular, diamo un'occhiata a un'app di esempio:

Dai un'occhiata all'implementazione dei moduli dell'app. All'interno di AppModule sono definite due route: la route predefinita associata a HomeComponent e una route nyan associata a NyanComponent:

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

Suddivisione del codice a livello di route

Per configurare la suddivisione del codice, è necessario rifattorizzare il percorso eager nyan.

La versione 8.1.0 dell'interfaccia a riga di comando di Angular può fare tutto per te con questo comando:

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

Verranno generati: - Un nuovo modulo di routing chiamato NyanModule - Un percorso in AppModule chiamato nyan che caricherà dinamicamente NyanModule - Un percorso predefinito in NyanModule - Un componente chiamato NyanComponent che verrà visualizzato quando l'utente raggiunge il percorso predefinito

Eseguiamo questi passaggi manualmente per comprendere meglio l'implementazione della suddivisione del codice con Angular.

Quando l'utente accede al percorso nyan, il router esegue il rendering di NyanComponent nell'outlet del router.

Per utilizzare la suddivisione del codice a livello di route in Angular, imposta la proprietà loadChildren della dichiarazione della route e combinala con un'importazione dinamica:

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

Ci sono due differenze principali rispetto al percorso eager:

  1. Hai impostato loadChildren invece di component. Quando utilizzi la suddivisione del codice a livello di route, devi fare riferimento a moduli caricati dinamicamente, anziché a componenti.
  2. In loadChildren, una volta risolta la promessa, restituisci NyanModule anziché puntare a NyanComponent.

Lo snippet riportato sopra specifica che, quando l'utente accede a nyan, Angular deve caricare dinamicamente nyan.module dalla directory nyan ed eseguire il rendering del componente associato alla route predefinita dichiarata nel modulo.

Puoi associare la route predefinita a un componente utilizzando questa dichiarazione:

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 {}

Questo codice esegue il rendering di NyanComponent quando l'utente accede a https://example.com/nyan.

Per verificare che il router Angular scarichi nyan.module in modalità lazy nel tuo ambiente locale:

  1. Premi "Ctrl+Maiusc+J" (o "Cmd+Opzione+J" su Mac) per aprire DevTools.
  2. Fai clic sulla scheda Rete.

  3. Fai clic su NYAN nell'app di esempio.

  4. Tieni presente che il file nyan-nyan-module.js viene visualizzato nella scheda Rete.

Caricamento lento dei bundle JavaScript con suddivisione del codice a livello di route

Trova questo esempio su GitHub.

Mostra un'icona di caricamento

Al momento, quando l'utente fa clic sul pulsante NYAN, l'applicazione non indica che sta caricando JavaScript in background. Per fornire un feedback all'utente durante il caricamento dello script, probabilmente vorrai aggiungere un'icona di caricamento.

Per farlo, inizia aggiungendo il markup per l'indicatore all'interno dell'elemento router-outlet in app.component.html:

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

Poi aggiungi una classe AppComponent per gestire gli eventi di routing. Questa classe imposterà il flag loading su true quando rileva l'evento RouteConfigLoadStart e imposterà il flag su false quando rileva l'evento 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;
        }
      }
    );
  }
}

Nell'esempio seguente abbiamo introdotto una latenza artificiale di 500 ms per consentirti di vedere lo spinner in azione.

Conclusione

Puoi ridurre le dimensioni del bundle delle tue applicazioni Angular applicando la suddivisione del codice a livello di route:

  1. Utilizza il generatore di moduli con caricamento lento dell'interfaccia a riga di comando di Angular per creare automaticamente lo scaffold di un percorso caricato dinamicamente.
  2. Aggiungi un indicatore di caricamento quando l'utente accede a un percorso con caricamento lento per mostrare che è in corso un'azione.