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 でビルドキャッシュがプレビュー機能として実装されていた時は、ビルドキャッシュを使用するには build
や serve
コマンドに 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秒程度短縮できました。プロジェクトの規模がとても小さいためビルド時間の減少幅は控えめですが、ビルドに数分かかるプロジェクトではより大きな効果を得ることができるでしょう。