Angular CLIのビルドキャッシュでCIの速度アップを目指す

JavaScript(TypeScript)のビルド時間が長い問題に頭を痛めている方々はとても多いと思います。大規模なプロジェクトではフルビルドに数分~10分超かかってしまうこともしばしば。今回は Angular 13 で標準機能となったビルド時のディスクキャッシュを利用して、そんな長くなってしまった CI ビルド時間の短縮を試みます。

試した環境

Angular CLI: 13.2.3
Node: 16.14.0
Package Manager: npm 8.5.0
OS: win32 x64

Angular: 13.2.2
... animations, cdk, common, compiler, compiler-cli, core, forms
... material, platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1302.3
@angular-devkit/build-angular   13.2.3
@angular-devkit/core            13.2.3
@angular-devkit/schematics      13.2.3
@angular/cli                    13.2.3
@schematics/angular             13.2.3
rxjs                            7.5.4
typescript                      4.5.5

今回試したプロジェクトはこちらに置いてあります。

Angular 13 のビルドキャッシュを使用する

Angular 12 でビルドキャッシュがプレビュー機能として実装されていた時は、ビルドキャッシュを使用するには buildserve コマンドに NG_PERSISTENT_BUILD_CACHE=1 という環境変数を設定する必要がありました。ビルドキャッシュが正式な機能となった Angular 13 ではデフォルトで ON になっていますので、環境変数などの設定は不要で、何もしなくてもビルドが高速化されています。 ビルドキャッシュの設定は angular.json 上で行うことができますが、触ることはあまり無いでしょう。

キー デフォルト値 説明
cli.cache.enabled true ビルドキャッシュの使用可否。
cli.cache.environment local ビルドキャッシュを使用する環境。 "local" "ci" "all" のいずれか。環境変数に "CI" の文字列があるか否かで挙動が変わるらしいです(未確認)。
cli.cache.path .angular/cache ビルドキャッシュの出力先ディレクトリー。

CI でビルドキャッシュを使用する

実際に Angular プロジェクトの CI 環境において、ビルドキャッシュを使用してみます。ビルドキャッシュのデフォルトの出力先は .angular/cache ですので、 CI のキャッシュ機能を利用してこのディレクトリーを CI ビルド間で使い回していきましょう。 CI ビルドが実行される度に(ビルド対象のソースが変更されている場合)キャッシュ内容が変更されていることが考えられるため、CI のキャッシュ機能のキーは固定とはせず、 TS や HTML 、 SCSS ファイルを全てチェックすることにします。
なお本記事では CI 環境として Azure Pipelines を使用します。 GitHub Actions にも同様のキャッシュ機能は存在しますので、使用感に大きな差は無いと思われます。シンプルなビルド定義ですので YAML 全量を掲載します。

variables:
  NG_CACHE_DIR_PATH: '.angular/cache'

pool:
  vmImage: ubuntu-latest

steps:
  - task: UseNode@1
    displayName: Install Node.js
    inputs:
      version: 16

  - task: Npm@1
    displayName: Install NPM Dependencies
    inputs:
      command: install

  - task: Cache@2
    displayName: Use Angular CLI Build Cache
    inputs:
      key: 'npm | "$(Agent.OS)" | package-lock.json | src/**/*.ts, src/**/*.html, src/**/*.scss'
      restoreKeys: |
        npm | "$(Agent.OS)" | package-lock.json
        npm | "$(Agent.OS)"
      path: $(NG_CACHE_DIR_PATH)

  - task: Npm@1
    displayName: Build Angular App
    inputs:
      command: custom
      customCommand: 'run build -- --configuration=production --output-path=$(Build.ArtifactStagingDirectory)'

  - task: PublishBuildArtifacts@1
    displayName: Publish Artifacts
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'drop'
      publishLocation: 'Container'

キャッシュタスクの設定で重要なポイントは TS や HTML 、 SCSS ファイルの差分をチェックする key に対して、キャッシュヒットしなかった場合に類似のキャッシュを検索する restoreKeys を設定することです。通常の開発時はファイルの中身に頻繁に変更が入りますので、既存のキャッシュがヒットしないことの方が多くなります。 restoreKeys はキャッシュヒットしなかった場合に、既存のキャッシュから設定値に合致するものを利用する機能です。 npm | "$(Agent.OS)" | package-lock.json と記述すると npm | "$(Agent.OS)" | package-lock.json | ** でヒットする最新のキャッシュが使用されます。 restoreKeys を設定することでファイルの中身が変更されてキャッシュがヒットしなかった場合でも前回ビルド時のキャッシュを使用することができます。

このビルド設定で、 Azure Pipelines 上で実行した結果は以下の通りです。初回、2回目以降(コード変更あり / コード変更無し)共に、3回実行した平均値です。初回 >>> 変更無し > 変更あり の実行時間になると予測していますが、どうでしょうか。(単位:秒)

初回 2回目以降
(変更無し)
2回目以降
(変更あり)
Node.js インストール 1.3 0.0 0.0
NPM 依存インストール 34.7 36.0 32.7
キャッシュ復元 7.0 12.3 7.7
Angular ビルド 34.0 14.3 15.0
キャッシュ保存 7.3 1.7 5.7
全体 96.0 74.3 70.3

変更あり無しで予想と異なる結果になりましたが、まぁ誤差の範囲内でしょう。Angular のビルド時間はビルドキャッシュによってコードに変更が無い場合は20秒程度、変更がある場合でも同程度短縮されています。また Pipeline のキャッシュタスクを使用したことによる増加時間は15秒弱で、このサンプルではキャッシュをそもそも使用しなかった場合と比較して5秒程度短縮できました。プロジェクトの規模がとても小さいためビルド時間の減少幅は控えめですが、ビルドに数分かかるプロジェクトではより大きな効果を得ることができるでしょう。

投稿者プロフィール
中谷 大造

中谷 大造

情報システム部の中谷です。社内用スクラッチアプリの開発をしています。

執筆記事一覧