Web Speed Hackathon 2022をやってみた

11月１日から27日まで開催されていた、webパフォーマンス改善のコンテスト「Web Speed Hackathon 2022 Public」に飛び入り参加してきました。

https://developers.cyberagent.co.jp/blog/archives/38227/

仕事の方ではバックエンド側ということもあり、フロントのパフォーマンス改善についてあまり詳しくはなかったのですが、先駆者の方々のブログやリポジトリを拝見しながらなんとか取り組んでみました。

## 初回計測は1点...
とりあえずLighthouseで計測してみましたがまさかの１点！
動作を確認するために操作するのにもストレスがかかるレベルの重さでした
![fe75e0dae10e9f5a90a348b0ee3bdac7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321767/dfce8e71-d04e-f607-65ce-dca6f75583b2.png)
これをなんとか改善していきましょう。

## webpack.config修正

まずはビルドをproductionとdevlopmentに分割するために、以下の構造と設定にしました。
```:ディレクトリ構成
root/
 ├ webpack.common.js // 汎用設定
 ├ webpack.dev.js    // 開発用設定
 ├ webpack.prod.js   // 本番用設定
 └ webpack.server.js // サーバーサイド用
```
```json:package.json
"build": "yarn build:server && webpack --config webpack.prod.js",
"build:server": "webpack --config webpack.server.js",
"build:dev": "yarn build:server && webpack --config webpack.dev.js",
"build:watch": "yarn build:dev --watch --progress",
```
webpackのファイル同士の設定をマージすることが出来る [webpack-merge](https://www.npmjs.com/package/webpack-merge)というモジュールを使用し、本番・開発用に汎用設定をマージすることでコマンドで使い分けることが出来るようにしました。
<br/>
次に、devlopmentに`webpack-bundle-analyzer`を導入しバンドルされたファイルのサイズを可視化します。こちらが出力結果です。

<img 
width="800" alt="before.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321767/c4b0ac7d-6d8f-1cb8-d5aa-95e26845222b.png">

表示されているsizeの概要は以下の通りです。
- stat size: 圧縮前のソースコードのサイズ
- parsed size: コンパイル後のバンドルファイルのサイズ
- gzipped size: gzip圧縮後のサイズ

とりあえず置き換え可能でありサイズが大きい物から削っていきました。

## バンドルサイズを削る

### Zengin-codeをモジュールからAPIへの取得に
**parsed size: -約3.1MB**
zengin-codeは一部で使用されるダイアログコンポーネントでのみ使用されており、すべてのページで使用されているわけではありません。そのため、ダイアログ起動時のタイミングで全銀システムが提供するAPIからデータ取得を行う方式にしました。

### ブラウザ対応関連のものを無くす
**parsed size: -約1.23MB**
レギュレーションに`Google Chrome 最新版`での動作が担保出来ればよい、と記述されていたため古いブラウザ対応のために使用されている以下moduleを削除しました。
- core-js
- es5-shim
- es6-shim
- es7-shim

### Fort AwesomeをモジュールからSVGへ
**parsed size: -約788KB**
アイコンにはFort Awesomeというアイコンフォントを提供するサービスが使用されていますが、アプリケーションでは3カ所でしか使用されていなかったため削ります。
モジュールによる提供だけではなくsvgによる提供を行っていたため、こちらに変更しました。

### Momentjsからdayjsへ
**parsed size: -約689KB**
Momentjsはバンドルサイズが大きいため、互換性が高くMomentjsと比べサイズの小さいDayjsに置き換えました。
また、サイズという観点以外にもMomentjsは2020年にメンテナンスモードに入っているためこちらの点からも置き換えるべきでしょう。

### lodashをやめる
**parsed size: -約480KB**
lodashで使用されているほとんどのメソッドは、ES20xxで追加されたメソッドを使用することで置き換え可能です。
下記のリポジトリを参考にしながら置き換えていきました。

https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore

## バンドルサイズ変更後
<img 
width="800" alt="after.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321767/a169c6da-eb5f-74df-b844-57a7dc4cf78e.png">

変更前の **9.49MBから1.98MB**まで削ることが出来ました！🎉
これ以外にもaxiosの削除・Preactの導入なども考えられましたが、今回は時間と知識の問題でここまでは行いませんでした...

## 画像・フォント最適化

### 画像最適化
imagesフォルダが85MBとかなり大きいのでこちらを最適化していきます

デフォルトでは画像をトリミングするコンポーネントを使用することで、使用箇所に合わせたリサイズを行っていましたが、最初からサイズに適した画像をこちらで準備すればコンポーネントで処理を行う必要がありません。

画像最適化ソフトは使ったことがなかったので、どれを選択すれば良いか困りましたが今回は「squoosh」を使用しました。採択理由は`@squoosh/cli`を使用することで複数画像をまとめてリサイズ・変換が可能だったためです。
```bash
squoosh-cli --webp '{quality: 70}' *.jpg
```
`@squoosh/cli`の使用方法に関してはこちらが参考になりました

https://zenn.dev/ruri14/articles/2f28c0e571e175

全ての画像をリサイズ・圧縮し、jepgからwebpに変換することで**85MB -> 225KB**まで削ることが出来ました

### フォント最適化
一部のページでwebフォントが使用されていましたが5.8MBと大きいのでこちらも削ります。まずはttfからwoff2に変換します。woff2は初めて知りましたがwoffと比べて 圧縮アルゴリズムとしてBrotli方式を採用していることで、woffと比べて12%程度圧縮率が高いらしいです。

さらに、フォントを使用している個所を確認すると数字と一部記号のみでしか使われていないためサブセット化を行い、使用されていないフォントを削除することでサイズを**5.8MB -> 3.8KB**まで削れました。

## その他

### gzip圧縮を行う
バックエンド側のレスポンスをgzip圧縮してから配信することでデータサイズを小さくします。
v3のfastifyでは、[fastify-compress](https://github.com/fastify/fastify-compress)を使用することで実現可能です。
```js:index.js
import fastifyCompress from "fastify-compress";

server.register(fastifyCompress, {
  encodings: ["gzip", "deflate"],
});
```
### CLSの改善
CLSとはgoogleが提唱する「Core Web Vitals(CWV)」というウェブパフォーマンスに関する指標の一つあり、ページの安定性を示すものです。

こちらの指標を向上させるために、以下の変更を行いました。
- 画像サイズを指定しておく
    - サイズを指定することで画像が読み込まれる前から領域を確保できる
- loding表示中の際に表示領域を指定しておく
    - loding完了後にfooterが大きく動くことを防ぐため、`height: 100vh`を指定
 
### 画像の遅延読み込み
最初に読み込まれる画像以外に`loading="lazy"`を指定することで、一部画像の遅延読み込みを行います。遅延読み込みを行うことでページの表示速度高速化につながります。

### font-display: swap
フォントの読み込みが終わるまでは代替えフォントで文字を表示します。文字を最初から表示させておくことでレイアウトシフトを予防しCLS改善に繋がります

### browserslistの更新
[こちら](#ブラウザ対応関連のものを無くす)と同様の理由でbrowserslistに最新版のchromeを指定しました。こちらを指定することでBableとPostCssのトランスパイル時に　サイズを最小限にしてくれます。

## 結果

Web Speed Hackathonでの点数は **459.75/500点**という結果でした！！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">450点超えた~!<br><br>&quot;Web Speed Hackathon 2022 Public&quot; に挑戦中です！<br>スコア 459.75 / 500.00 で、現在 13 位です <a href="https://twitter.com/hashtag/WebSpeedHackathon?src=hash&amp;ref_src=twsrc%5Etfw">#WebSpeedHackathon</a><a href="https://t.co/Bzq8ekPctW">https://t.co/Bzq8ekPctW</a> <a href="https://twitter.com/hashtag/WebSpeedHackathon?src=hash&amp;ref_src=twsrc%5Etfw">#WebSpeedHackathon</a></p>&mdash; 吉田 (@vitamin_yoshida) <a href="https://twitter.com/vitamin_yoshida/status/1596829753511538688?ref_src=twsrc%5Etfw">November 27, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


lighthouseで見ても改善前と比べ大幅にパフォーマンスが上がっていますね！
![ddd2a765ef1644c01f2747ef8165001a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321767/ebb05d04-8f9b-deed-1a5b-5448620d7426.png)

300点を超えるのが目標だったので個人的には満足です。ただ採点にはややブレがあるため上振れた感は否めません（笑）

## 反省点
バンドルサイズの軽減・画像とフォントの最適化以外のことは余りできなかったのは心残りです。
特にmemo化やCloudFlareのCDNを利用した高速化はやってみたのですが期限までに実装できませんでした。

また、画像のリサイズ周りでVPTに何度も引っかかってしまい大幅に時間を取ってしまいました... ローカルでテストを実行する方法があったようなので、次の機会がこちらを試してみたいです

https://zenn.dev/laiso/articles/4fbdee70535d54

## 感想

今後はこちらのブログを見ながらどのような改善点があったのか確認していきたいと思います！

https://yusukebe.com/posts/2022/wsh/

フロント側の速度改善方法を実践的に学ぶことが出来てとても楽しかったです！
開催してくださったCyberAgentさんありがとうございました！今まで無料デプロイを提供していたherokuにも感謝！

## その他参考文献
- [Web Speed Hackathon 2021 miniでほぼ満点を出しました](https://trap.jp/post/1481/)
- [webpackで開発用/本番用の設定を分ける](https://qiita.com/teinen_qiita/items/4e828ac30221efb624e1)
- [lodash やめ方](https://qiita.com/mizchi/items/af17f45d5653b76f6751)
- [JavaScript での時刻操作に Moment.js ではなく Day.js を利用し続けている理由](https://d.potato4d.me/entry/20200921-migrate-to-dayjs/)
- [WebFont の WOFF2 対応によるサイズ最適化](https://blog.jxck.io/entries/2018-02-13/web-font-woff2.html
)
- [Web Vitals の概要: サイトの健全性を示す重要指標](https://developers-jp.googleblog.com/2020/05/web-vitals.html)