# Web Speed Hackathon 解説会
:::info
間違いとかを見つけたら @cp20 宛てに報告してもらえると嬉しいです
:::
## 前提知識
Q. パフォーマンス (が良い) とは何か?
A. 一般には同じ動作を短い時間 (コンピューティングリソース) で行えること。Webの世界においては通常 Core Web Vitals のような指標で測られる。
Q. Core Web Vitals とは?
A. LCP (Largest Contentful Paint) / INP (Interaction to Next Paint) / CLS (Cumulative Layout Shift) の3つのメトリクスを合わせたもの。Core Web Vitals の他にも様々なパフォーマンスを測る指標がある。
## パフォーマンスの指標
### WSHで使われる指標
- [**FCP (First Contentful Paint)**](https://web.dev/articles/fcp)
- ページを読み込み始めてから初めに (first) 意味のある (contentful) もの (paint) が表示されるまでの時間 (短い方が良い)
- レンダリングをブロックするものを少なくすると改善される
- CSSを削る (minify)
- JSを削る (splitting / minify / terser)
- preconnect / preload
- 無駄なpreload (リクエスト) を削る
- (WSHでは意味がないが) キャッシュ
- [**LCP (Largest Contentful Paint)**](https://web.dev/articles/lcp)
- ページを読み込み始めてから最も大きい (largest) 意味のある (contentful) もの (paint) が表示されるまでの時間 (短い方が良い)
- LCPの要素の読み込みを速くすると改善される
- Lighthouse などを使ってLCP要素を特定して、読み込みを速くする
- 大抵 `<img>` とかなので `loading="eager"` をつける
- [**INP (Interaction to Next Paint)**](https://web.dev/articles/inp)
- 操作 (interaction) してからそれが画面 (paint) に反映されるまでの時間 (短い方が良い)
- [スクリプトの実行時間を短くする (メインスレッドの占有を避ける) などで改善される](https://web.dev/articles/optimize-inp)
- 60fpsだとしたら、1フレームに17msを超えるぐらいの処理が挟まるとカクつきを感じる
- [**CLS (Cumulative Layout Shift)**](https://web.dev/articles/cls)
- 要素のズレがどれだけ起きるか (起きない方が良い)
- 画像が後から読み込まれるなどすると画像の幅の分だけ後ろのコンテンツがずれる、ということが起きる
- [要素をなるべくズレないようにすると改善される](https://web.dev/articles/optimize-cls)
- 後から読み込む要素 (画像など) は最初からその大きさを確保しておく (`width` とか `height` とか)
- [**TBT (Total Blocking Time)**](https://web.dev/articles/tbt)
- スクリプトがどれだけメインスレッドをブロックする時間 (短い方が良い)
- 50msを超えてメインスレッドを占有すると、ブロックしているとみなされる
- メインスレッドでスクリプトを長時間実行しないようにすると改善される
- 実行されるJSを少なくする
- Service Workerに処理を分ける (Service Workerはメインスレッドとは別のスレッドで動く)
- [**SI (Speed Index)**](https://developer.chrome.com/docs/lighthouse/performance/speed-index)
- コンテンツが視覚的にどれだけ速く描画されるかの指標
- JSの実行時間を削るなどすると改善される
### [WSHのスコア計算方法](https://github.com/CyberAgentHack/web-speed-hackathon-2024/blob/main/docs/scoring.md)
- ページランディング
- FCP×10 + SI×10 + LCP×25 + TBT×30 + CLS×25
- ユーザーフロー
- TBT×25 + SI×25
## WSH典型テクニック
WSHでは意図的にデチューニング (= 遅く) している部分がそれなりにあるので、これらを素直な実装に戻せば高速化が見込める
### ビルド最適化
だいたい `minify: false` とか `splitting: false` とかされていたり、polyfillが入れられていたり、`target` が古かったりする
ビルド設定変えるだけで多少は良くなる
### バンドルサイズ削減
#### Bundle Analyzerを入れる
「(ビルドツール) bundle analyzer」で検索して適当に設定する
バンドルサイズへの影響が大きいやつから対処していく
#### 変なやつを消す
超長い文字列がソースコード中に埋め込まれてたりしたらだいたい消せる
- 画像 (base64) -> そのまま画像にする
- テキスト -> 適当にlazy loading
#### minify する / terser かける
良い感じにビルドの設定する
#### 無駄なライブラリを削る
- polyfillとかshimとかは (変なことがない限り) いらないので削る
- polyfill: 古いブラウザでも新しい機能を使えるようにする
- shim: polyfillみたいなやつ(?)
- Chrome最新版で実行する前提なので、どっちもいらない
- 簡単な動作を妙に重いライブラリでやろうとしている部分を書き換える
- 1,2箇所でしか使われていないライブラリ (特にバンドルサイズが大きいやつ) は自前実装に置き換えられないかを検討する
- ↑だいたい置き換えられる
- コメントが書いてあることが多いので、そういうときはコメントの内容を素直に実装する
- utility libraryを消す (↑に含まれると言えば含まれる)
- 有名なのは lodash とか
- 別のライブラリの奥 (依存関係) に隠れてたりもする (がそういう場合は対処が難しいかも)
### 画像最適化
- `.webp` とか `.avif` みたいな圧縮率が高い画像形式を使うようにする
- `.avif` の方が圧縮率が高いと言われているが、圧縮のコストがかなり大きいので事前に圧縮できるときだけの方が良さそう
- 圧縮する
- `sharp` とかを使って圧縮する
- qualityを適当に下げてやると良いと思う (30ぐらいまで下げても割と大丈夫(大会では))
- 大きさを最適化する
- 無駄に大きかったら適当にリサイズする
- ヒーロー画像 とかは `<picture>` `<source>` を使って画面サイズごとに出し分けると良さそう
- だいたいLCPになるので最適化する
- `loading="eager"` とかつけとこう
- `loading="lazy"` する
- LCPの画像以外は `loading="lazy"` しておけばよい
- CLS対策で `width` と `height` を設定する
### CLS対策
- SSR (Server Side Rendering) or スケルトン
### リクエスト最適化
- いわゆる N+1 リクエストが発生してないか
- 良い感じに巻き上げる
- サーバーからのレスポンスが無駄に大きくないか
- 特に JOIN が発生してるのに使ってないところとかは削ると良い
### ReDoS
- 最近毎回出てるから典型ということにする
- 細かい説明は置いておいて「評価に時間がかかる正規表現」が良くないということが分かっていればよい
- 素直な正規表現に書き換える
### フォントの最適化
- 必要な weight だけ読み込む
- 必要な subset だけ読み込む
- [ごく一部しか使っていない場合](https://www.webcreatorbox.com/tech/google-fonts-subset)
- [普通に使っている場合](https://gwfh.mranftl.com/fonts)
### CSS頑張る
JSで行われている処理のうち、CSSに置き換えられるもの (リサイズとかアニメーションとか) があれば置き替えた方がだいたいパフォーマンスが良くなる
CSS力が求められる
### 圧縮
- gzip / brotli / zstd
- 困ったらbrotli (もしくはgzip)
- middlewareとかで簡単に入れられると思う