インフラジスティックス・ジャパン株式会社Blog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

Angularアプリのパフォーマンスを向上させるには?

Angularは、モダンなWebアプリケーションを開発するためのフレームワークとして非常に人気があり、広く採用されています。このテクノロジーは非常にパワフルで機能が豊富です。Web開発者として必要なものはすべてすぐに利用でき、Angularを使用するとフレームワークの上に構築されたアプリケーションの設定、メンテナンス、拡張を簡単に行うことができます。

皆さんもおそらくこれまでに1つ以上のAngularアプリケーションを組み上げた経験があると思いますが、それらはパフォーマンスとして最適なものでしょうか?

ソフトウェアパフォーマンスについて書いた記事シリーズのパート2では、Angularの最適化について説明し、私が構築したAngularのサンプルアプリを使ってAngularアプリケーションのパフォーマンスを改善する方法を実演します。Chrome Dev Toolsを使って最初のLighthouse(ライトハウス)スコアを導き出し、アプリケーションの初期状態を判定します。何をどのように改善できるか見てみましょう。

このシリーズの他のブログ: Software Performance [Web] Part I https://medium.com/ignite-ui/software-performance-web-61158c8583d

この記事の原文は以下よりご確認いただけます。
Konstantin Dinev / Friday, August 4, 2023 How To Improve the Performance of Angular Apps?

TRY IGNITE UI FOR ANGULAR

Angularアプリケーションのパフォーマンスを改善する方法

この記事では、私が作成したAngularアプリケーションのサンプルを使用します。この記事の執筆時点で、このアプリケーションは以下の機能とライブラリを使用しています:

  • Angular 16
  • Ignite UI for Angular 16
  • RxJS 7
  • PWA(Angularサービスワーカー)
  • サーバーサイドレンダリング(expressサーバー)
  • Angularローカリゼーション

Angularビルド

開発環境でアプリケーションを実行しているときはすべてが問題なく動作しているように見えますが、最初のLighthouseスコアはあまり高くありません:

スコアの低いセクションで提案された改善案を見ると、どこに問題があるのかがわかります。まず大きな問題は、クライアントに転送されるリソース(JavaScript、スタイル、静的リソース)のサイズです。

この問題は、開発用ではなく本番用のAngularアプリケーションのビルドを実行することで簡単に解決できます。 デプロイする前には必ず本番用の構成でビルドしてください。そうすることで、JavaScriptとCSSのサイズを小さくするという警告が解消されます。Angularリポジトリのルートにあるangular.jsonファイルを見て、本番ビルドがどのように異なるか見てみましょう:

"configurations": {
  "production": {
    "budgets": [
      {
        "type": "initial",
        "maximumWarning": "2mb",
        "maximumError": "5mb"
      },
      {
        "type": "anyComponentStyle",
        "maximumWarning": "10kb"
      }
    ],
    "fileReplacements": [
      {
        "replace": "projects/common/src/environments/environment.ts",
        "with": "projects/common/src/environments/environment.prod.ts"
      }
    ],
    "localize": true,
    "optimization": true,
    "outputHashing": "all",
    "sourceMap": false,
    "namedChunks": false,
    "extractLicenses": true,
    "vendorChunk": false,
    "buildOptimizer": true,
    "serviceWorker": true,
    "i18nMissingTranslation": "error",
    "ngswConfigPath": "projects/bellumgens/src/ngsw-config.json"
  },
  "bg": {
    "localize": [
      "bg"
    ]
  }
}

ここにはかなり多くの設定がありますが、ここで最も重要なのは「"optimization": true」です。本番用の構成でアプリケーションを実行すると、ロード時間のパフォーマンスという点で、スコアの違いが顕著になります:

改善案のリストをもう一度見てみると、提案の数がはるかに少なくなっています。 テキスト圧縮については未使用のJavaScriptと静的リソースのキャッシュが最大の改善案として挙げられています:

Angularのプリレンダリングとサーバーサイド・レンダリング

Angularはシングルページアプリケーション(SPA)フレームワークです。デフォルトでは、アプリケーションのライフサイクルはクライアントからのリクエストに応じてアプリケーションをホストするサーバーが必要なスクリプトとスタイル参照をすべて含むHTMLファイルを提供するようになっています。しかし、本文は空です。スクリプトとスタイルがリクエストされ、提供されると、アプリケーションはブートストラップされ、指定されたアプリケーションのJavaScriptロジックに基づいてコンテンツが設定されます。Angularは、最初のHTMLドキュメントで実際のコンテンツを提供することで、このライフサイクルを改善する2つのメカニズムを提供します。これを行うには、ドキュメントを提供する前にアプリケーションのJavaScriptロジックを実行する必要があります。その方法は

  • ビルド時(プリレンダリング) - 静的なコンテンツがほとんどのページの場合。
  • サーバー上での実行時(サーバーサイド・レンダリング) - リクエストごとに最新のコンテンツを配信する必要がある、より動的なコンテンツを含むページの場合。

Angularのサンプルアプリでサーバーサイドレンダリングを有効にし、expressエンジンを使ってテキスト圧縮と静的リソースキャッシュを有効にしています。これを行うには、Express サーバー ロジックに次のコードを追加します:

export const app = (lang: string) => {
  // server scaffolded by [ng add @nguniversal/express-engine]
...
  // enable compression [npm install compression]
  const compression = require('compression');
  server.use(compression());
...
  // Serve static files from /browser with 1y caching
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));
...
}

サーバーサイド・レンダリングでアプリを提供し、Lighthouse テストを再度実行してみます。初期ロードはさらに改善され、ファースト コンテントフル・ペイント(ページの読み込みが開始されてから、ページ内のいずれかのコンテンツが初めて描画されるまでの時間)は1秒未満になり、速度指数は1.2秒に短縮されました。

Angularの最適化のために残された改善案は、未使用のJavaScriptとCSSを減らすことです。

これらに対処するには、アプリケーションのリファクタリングを行う必要があります。

Angular の遅延読み込み

未使用のJavaScriptを減らすために、最善のアプローチは遅延ロードされたルートを作成することです。これにより、Angularフレームワークにトップレベルのモジュールでどのコンポーネントが不要であるかを伝え、JavaScriptをモジュールに分割し、要求されたルートがロードされたときにのみそのロジックがロードされるようにします。

このブログで使用しているAngularのサンプルアプリは、ホームルートの一部でない、igx-gridのような大きなコンポーネントを利用しています。このコンポーネントを使うルートを別のモジュールに分離しています。こうすることで、コンポーネントを使用するルートがロードされて初めて、そのコンポーネントがロードされるようになります。モジュールを分離すると、ルートは次のようになります:

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'register', component: RegistrationComponent },
  { path: 'unauthorized', redirectTo: 'unauthorized/', pathMatch: 'full' },
  { path: 'unauthorized/:message', component: UnauthorizedComponent },
  { path: 'emailconfirm', component: EmailconfirmComponent },
  { path: 'strategies', loadChildren: () => import('./strategies/strategies.module').then(m => m.StrategiesModule) },
  { path: 'emailconfirm/:error', component: EmailconfirmComponent },
  { path: 'players', loadChildren: () => import('./player-section/player.module').then(m => m.PlayerModule) },
  { path: 'team', loadChildren: () => import('./team-section/team.module').then(m => m.TeamModule) },
  { path: 'notifications', loadChildren: () => import('./notifications/notifications.module').then(m => m.NotificationsModule) },
  { path: 'search/teams/:query', component: TeamResultsComponent },
  { path: 'search/players/:query', component: PlayerResultsComponent },
  { path: '**', component: HomeComponent }
]

team.moduleは私が使用しているグリッドをロードするもので、コードは次のようになります:

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    // ...
    IgxGridModule,
    // ...
    TeamComponent,
    // ...
  ]
})
export class TeamModule { }

ビルドを見ると、これが最初の分割の結果です:

Angularアプリケーションのパフォーマンスを向上させるために必要なもう1つのことは、使用するスタイルのCSSサイズを制限することです。私はUIライブラリとしてIgnite UI for Angularhttps://jp.infragistics.com/products/ignite-ui-angularを使用していますが、コンポーネントテーマのカスタマイズと最適化に関する素晴らしいハウツー記事https://jp.infragistics.com/products/ignite-ui-angular/angular/components/general/how-to/how-to-customize-theme#theme-optimizationがあります。

画像の最適化

Angularの最適化で実行すべきもう一つの側面は、画像の最適化です。Lighthouseチェックでは、種類やサイズによって最適でない画像を使用しているかどうかがわかります。読み込む画像がコンテナより大きくないこと、また最適なエンコーディングであることを確認してください。私は現在、画像に.webp形式を使っています。少し画質が落ちますが、アプリケーションには最高の忠実度の画像は必要ありません。

画像は読み込まれた後にレイアウトのずれを引き起こします。そのため、画像を読み込む前にブラウザが寸法を把握できるよう、画像にwidth属性とheight属性を設定することをお勧めします。画像のアスペクト比設定(幅と高さ)がない場合、Lighthouseは警告を表示します。これにより、レイアウトのずれを軽減または完全になくすことができます。

画像のベストプラクティスを実行するNgOptimizedImage

Angularは画像のベストプラクティスを実施し、画像の読み込みを最適化するディレクティブを公開しています。これはNgOptimizedImageと呼ばれるもので、使い方はとても簡単です。NgModuleベースのAngularアプリケーションであればCommonModuleをインポートする、もしくは、使用したいコンポーネントでNgOptimizedImageをインポートするだけです。そして、画像のソース属性を設定する際にsrcを使用する代わりに、ngSrcを使用します。

<img ngSrc="/assets/wallpapers/strat-editor.webp" width="600" height="347" class="preview-image" alt="Strategy Editor" />

最後に、各画像のロードの優先順位をさらに指定し、重要でない画像はすべて遅延ロードするようにアプリに指示することができます。width属性とheight属性を削除すると、アプリの実行結果は次のようになります:

以上です。

まとめ

Angularアプリケーションのパフォーマンスを改善するには、さまざまな条件下でアプリが効率的かつ確実に動作するように、継続的なモニタリング、最適化、ベストプラクティスが必要かもしれません。しかし最終的には、これらが究極のUXを提供し、ユーザーを引き付けて維持し、競争力を維持し、ビジネスの成功を達成する方法なのです。