Angularアプリのパフォーマンス改善記

だんだんとクリスマスが近づいてきましたね :snowman2: 
アドベントカレンダー21日目、今日はAngular製社内ツールのパフォーマンス改善をした記録を残します。

## 初期状態 (16)
![perf16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318825/abff86e6-232a-a599-2943-1091126663c8.jpeg)
今回は手っ取り早くライトハウスの計測スコアを指標しながら進めていきたいと思います。最初の状態の計測では16でした。背景情報として、今回のパフォーマンス改善対象はピュアにAngular+NgRxで構成されていて、しがらみや闇のIE対応もないプロジェクトだったので比較的スコアは上げやすい状況だったと思います。

## 依存ライブラリの見直し (16 → 54)
ここはちょっとAngularとはあまり関係ない話になりますが、手始めにサイズが大きすぎる依存ライブラリを見直すことにしました。
以前別のプロジェクトで重すぎるmoment.jsをdayjsに置き換えたことがあったので、まずはそこから手をつけることに。調べてみるとプロジェクト内で一箇所しか使っていなかったので置き換え自体は簡単でした。

しかし、成果を確認するためにwebpack-bundle-analyzerをかけると**なんとまだバンドルに生きています**。よく調べてみると、依存しているhandson-tableがmoment.jsに依存していることが判明しました。handson-tableをフル活用していた場合はここで諦めざるを得なかったのだと思いますが、今回の場合エクセル風にデータを表示したかっただけで全く編集機能を使っていなかったので、この際と思ってhandson-tableも捨てて自前で同じ見た目のtableを組むことにしました。第一段階の成果は38upとなかなかの滑り出しでした。

**教訓：依存を追加する際はそのライブラリが本当に必要なのか、オーバースペックでないか立ち止まって考えよう**

![perf54.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318825/0c97e263-b576-166e-dffa-93847cd0e79f.jpeg)

## モジュール周りの見直し (54 → 72)
### importsの見直し
地道に各モジュールにいらないものがimportされていないか確認していきます。
普段からきちんとしていればそこまで大きくスコアが変わることはないかと思いますが、今回の場合細かい単位でimportできるコンポーネントライブラリを使っているにも関わらず、うっかり全部入りモジュールをルートで読み込んでいる箇所があったため、その削減だけで結構成果が出てしまいました。ただ、そういった極端なケースをのぞいても、長く複数人で開発しているとどうしてもいらないモジュールのimportが混ざりがちなので定期的に確認するといいと思います。

### 構成の見直し
主にやったことは、初期ロードに必要なモジュールの切り出しです。
例えば、もともとはcomponentsというフォルダ内にツール内のコンポーネントを格納してほぼ全てのモジュールで読み込んでいたのですが、これをやめて①初期ロードから必要でその後どこでも使うものと②初期ロード以外のページだけで必要なものに分けたり、pagesでひとまとめにしていたものから初期ページだけ抜き出すなどの整理を行いました。

これら合計で18upとなりました。
**教訓:モジュールはこまめに分けて、importに無駄なものを書かない意識を持とう**
![perf72.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318825/9310d363-94a1-ddbe-f6bb-c125c28eee6e.jpeg)

## CSSのLazyLoad (0)
依存している3rd partyライブラリの必要とするCSSは普通にやると`angular.json`のstylesのとこに並べることになり、普通に初期リソースの一部としてロードされると思います。今回の対象プロジェクトも例に漏れずそうだったのでそれをLazyLoadするように変更しました。これは過程でデフォルトのdevモードではcssもjsとしてロードされるということを知れたり、rel属性についておさらいしたりと学びが多かったので楽しかったです。対象のCSSが１つしかなかったこともあり、スコアとしては変動なしでした。

## Routingの見直し (73 → 93)
一般的には、「初期ロードでいらないモジュールは遅延ロードしようね！」ということを気をつけるべきだと思うのですが、今回のプロジェクトではむしろ全てが遅延ロードされてしまっていたため、「初期パスを遅延ロードから外す」作業をしました。これにより、初期ロードで飛ばすリクエスト数を最小限に戻すことができ、速度改善に繋がるという寸法です。これが意外と上がり幅が大きく、20upを遂げて90の大台に乗りました。

**教訓:LazyLoadしよう、ただし初期パスは除くこと**
![IMG_9698.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318825/09507409-00c5-bd1d-260b-ebd11ddf8cd6.jpeg)

## 気をつけたこと
- importの整理などをするために各ファイルをのぞいていると、いらない変数がたまたま残っていたりしてついでに整理したくなってしまいますが、そういったついで用事は別MRでまとめるようにしました。というのも、万が一デグレが起きたときに原因を探しにくくなるのと、得てしてMRの内容が大きくなりがちなのでレビュワーの負担を減らすためです。
- 今回のプロジェクトは比較的好きにできるプロジェクトだったので色々と実験も兼ねてやってみましたが、実際にプロダクトとして出ていくプロジェクトでパフォーマンス改善をする際はある程度以上効果の見込めるものに限って行いました。また、レビュアーが変更がメリットに見合うか検討しやすいようにできる限り向上幅を確認できる情報をMRに記載するようにしました(例えばwebpack-bundle-analyzerのbefore/afterキャプチャなど)。

## その他の手段について
- brotli圧縮 (CloudFront使ってるのでLambda@Edgeでごにょごにょすることになりそう)
- 画像をwebpにする(やったのですが、前後でスコアの記録を残してなかったのと半年くらい前であまり記憶がないので省きました。確かそこそこ効果があったような…)
- lodashをlodashのメソッドごとの個別パッケージに置き換える(効果はあったものの、置き換えたあとデグレが生じたので戻しました。その後lodash-esにしたところ上手く行きましたが、確かjestと相性が悪かったです)

## おまけ
- CDS2019のセッションの動画をみて、LightHouseCIがでたことを知ったので早速pre-mergeとしてセットアップしてみました。時を経て様々な変更が入ることで放っておくとパフォーマンスは劣化しがちなので、MRをレビューするタイミングで確認できるのは嬉しいです。何点以下でエラーを投げるという設定ができるのですが、あまりに志高くしていると(例えばこのプロジェクトでいうと90)毎回のpre-mergeの誤差で落ちたり落ちなかったりという嬉しくないことになるのでそこだけ気をつけています。

- あれこれ調べて試して計測して…のトライアルアンドエラーを重ねるのは楽しかったです。これが初めてのパフォーマンス改善だったので、まだまだ何をするとどれくらいの効果が見込めそうかという勘所はつかめていませんが、個人プロジェクトでも取り組んでみたいと思います。

- それでは、みなさん素敵な冬休みをお過ごしください :dog2: :gift: