Angular でのルートレベルのコード分割

ルートレベルでコードを分割することで、アプリのパフォーマンスを改善できます。

この投稿では、Angular アプリケーションでルートレベルのコード分割を設定する方法について説明します。これにより、JavaScript バンドルのサイズが小さくなり、操作可能になるまでの時間を大幅に短縮できます。

この記事のコードサンプルは GitHub にあります。積極的なルーティングの例は、eager ブランチで入手できます。ルートレベルのコード分割の例は、遅延ブランチにあります。

コード分割が重要である理由

ウェブ アプリケーションの複雑さが増すにつれ、ユーザーに提供される JavaScript の量は大幅に増加しています。JavaScript ファイルのサイズが大きいと、インタラクティビティが著しく遅れることがあるため、特にモバイルでは高額なリソースになる可能性があります。

アプリケーションの機能を犠牲にすることなく JavaScript バンドルを縮小する最も効率的な方法は、積極的なコード分割を導入することです。

コード分割を使用すると、アプリケーションの JavaScript を異なるルートや機能に関連付けられた複数のチャンクに分割できます。この方法では、ユーザーが必要とする JavaScript をアプリケーションの初回読み込み時にのみ送信するため、読み込み時間が短縮されます。

コード分割の手法

コードの分割は、コンポーネント レベルとルートレベルの 2 つのレベルで行うことができます。

  • コンポーネント レベルのコード分割では、コンポーネントを独自の JavaScript チャンクに移動し、必要に応じて遅延読み込みを行います。
  • ルートレベルのコード分割では、各ルートの機能を個別のチャンクにカプセル化します。ユーザーがアプリケーションを操作すると、個々のルートに関連付けられたチャンクを取得し、必要に応じて関連する機能を取得します。

この投稿では、Angular でルートレベルの分割を設定する方法について説明します。

サンプル アプリケーション

Angular でルートレベルのコード分割を使用する方法を詳しく見ていく前に、サンプルアプリを見てみましょう。

アプリのモジュールの実装を確認します。AppModule 内には、HomeComponent に関連付けられたデフォルト ルートと、NyanComponent に関連付けられた nyan ルートの 2 つのルートが定義されています。

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

ルートレベルでのコード分割

コード分割を設定するには、nyan eager ルートをリファクタリングする必要があります。

Angular CLI のバージョン 8.1.0 では、次のコマンドですべての処理を行うことができます。

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

これにより、次のものが生成されます。 - NyanModule という新しいルーティング モジュール - NyanModule を動的に読み込む、AppModule 内の nyan というルート - NyanModule のデフォルト ルート - ユーザーがデフォルト ルートにヒットしたときにレンダリングされる NyanComponent というコンポーネント

Angular によるコード分割の実装について理解を深めるために、これらのステップを手動で見ていきましょう。

ユーザーが nyan ルートに移動すると、ルーターはルーターのコンセントに NyanComponent をレンダリングします。

Angular でルートレベルのコード分割を使用するには、ルート宣言の loadChildren プロパティを設定し、動的インポートと組み合わせます。

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

eager ルートとの主な違いは 2 つあります。

  1. component ではなく loadChildren を設定している。ルートレベルでコード分割を使用する場合は、コンポーネントではなく、動的に読み込まれるモジュールを指定する必要があります。
  2. loadChildren では、Promise が解決されると、NyanComponent を指すのではなく、NyanModule を返します。

上記のスニペットは、ユーザーが nyan に移動したときに、Angular が nyan ディレクトリから nyan.module を動的に読み込み、モジュールで宣言されたデフォルト ルートに関連付けられたコンポーネントをレンダリングするように指定します。

この宣言を使用して、デフォルト ルートをコンポーネントに関連付けることができます。

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

このコードは、ユーザーが https://example.com/nyan に移動すると NyanComponent をレンダリングします。

ローカル環境で Angular ルーターが nyan.module を遅延ダウンロードすることを確認するには:

  1. `Ctrl+Shift+J` キー(Mac の場合は `Command+Option+J`)を押して、DevTools を開きます。
  2. [ネットワーク] タブをクリックします。

  3. サンプルアプリで [NYAN] をクリックします。

  4. nyan-nyan-module.js ファイルは [ネットワーク] タブに表示されます。

ルートレベルでのコード分割を使用した JavaScript バンドルの遅延読み込み

GitHub でこの例をご覧ください。

スピナーを表示する

現時点では、ユーザーが [NYAN] ボタンをクリックしても、アプリはバックグラウンドで JavaScript を読み込んでいることを通知しません。スクリプトの読み込み中にユーザーにフィードバックを提供するには、スピナーを追加することをおすすめします。

そのためには、まず app.component.htmlrouter-outlet 要素内にインジケーターのマークアップを追加します。

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

次に、ルーティング イベントを処理する AppComponent クラスを追加します。このクラスは、RouteConfigLoadStart イベントを認識すると loading フラグを true に設定し、RouteConfigLoadEnd イベントを認識するとフラグを false に設定します。

@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 の遅延読み込みのモジュール ジェネレータを使用して、動的に読み込まれるルートを自動的にスキャフォールディングします。
  2. ユーザーが遅延ルートに移動したときに読み込みインジケーターを追加して、進行中のアクションがあることを示す。