Web Speed Hackathon 2021 mini に参加しました
Web Speed Hackathon 2021 mini が2021年12月4日〜2022年1月3日にかけて開催され,これに参加してきました.重たい短文投稿サイトをチューニングし高速化することを目指すコンテストです.
自分が参加できたのは終了直前の数日だけなのですが,多くのことを学ぶことができました.
スコアはLighthouse を用いて計測されます(→詳細).満点は720点です.
初期計測で79.02点,最終計測で649.21点です.最終9位でした.振り返りつつ,復習のために行ったことを記録しておきます.
対応するコミットをそれぞれの項目に記載しましたが,一つのコミットに複数の変更が入っている場合があります.また,各項目はジャンルごとに並んでおり,順番と効果は関係しません.
- やったこと
- もっとできそうなこと
やったこと
NODE_ENV
初期実装ではトランスパイル後のCSSが6.5MB,JavaScriptが12.5MBもあります.NODE_ENV=development
が適用されていたので修正しました.modeとsource-map設定についても変更します.このあたりは,2020年に開催されたWeb Speed Hackathon Online Vol.1 の解説を参考にしつつ作業しました.
webPack
Tailwind CSSのpurge
Purgeオプションを設定し,未使用のスタイルを除去します.
当該コミット: 5d53909
cssnanoの導入
cssの最適化を行います.
当該コミット:8a9c1fc
Babel
今回のターゲットブラウザは Chrome 最新版だけなので,必要な分だけトランスパイルしました.
assetsの軽量化
WOFF2を使うようにする
WOFF2はWOFFよりも圧縮率が高いとされています.今では多くのブラウザが対応しているようです.Can I use WOFF2
当該コミット:81031e8
Font Awesomeの必要なアイコンだけ読み込み
使わないアイコンを削除して読み込むファイルサイズを縮小しました.アイコンごとに別のファイルにしても良かったかもしれません.
投稿画像のリサイズとwebPへの変換
画像が表示サイズに比べてかなり大きかったのでリサイズしました.また,webPへの置き換えを行いました.圧縮率の観点から,AVIFへ置き換えたほうが効果が高いかもしれません.
当該コミット:ec719ec
661266d
273c014
アイコン用画像のリサイズとwebPへの変換
投稿画像と同様に,リサイズとwebPへの置き換えを行いました.
GIFのリサイズとwebMへの置き換え
GIFをリサイズしつつ,より軽量なwebMに置き換えました. 動画圧縮コーデックはVP9を利用しましたが,圧縮率の観点から考えるとAV1のほうが良いかもしれません.
依存パッケージの最適化
moment
必要な部分だけ読み込み
webpack-bundle-analyzer を見つつJavaScriptファイルを小さくします.moment(71.9kB)がデカかったのでmoment-locales-webpack-pluginを使って必要な部分だけ読むようにしました.
※ 最終的にmomentを使わずに実装できました(次項参照).
当該コミット:6252ff6
momentの削除
moment(71.9kB)は一部でしか使っておらず,自前で実装できそうです.結局,実装を置き換えて使用を取りやめました.
当該コミット:2b762c0
Preactへの置き換え
webpack-bundle-analyzer 上でReact(2.8kB)とReact DOM(39.4kB)がかなり大きく表示されています.より軽量なPreact(4kB)を使用しました.
当該コミット:2fc58d9
bluebird(gifler,omggifを使わない)
GIFからwebMに変更した結果,gifler,omggifが不要になりました.giflerに必要だったbluebird(21.7kB)も読み込まれなくなりました.
当該コミット:6841907
lodash
lodash(24.5kB)を使わずに実装できたので除去しました.
当該コミット:f47c83d
jQuery
jQuery(30.3kB)のajaxによる実装を,fetch APIに置き換えました.
当該コミット:4b7bb29
pako
クライアントからサーバーへの送信ではgzip圧縮を諦め,pako(13.5kB)を取り除きました.
当該コミット:4b7bb29
AudioContextの読み込みを削除
音の波形をバックエンド側で計算するので,不要になったAudioContextを消しました.
当該コミット:a624800
image-size
画像のトリミングをCSSのobject-cover
で行うようにしたので,image-size(4.8kB)は使わなくなりました.
normalize.cssを読み込まない
Tailwind CSSにリセットCSSが含まれているので,normalize.cssは(おそらく)不要です.
当該コミット: 6cc52a8
webfont.css
使用していないフォントをwebfont.cssから削除
font-weightを見ると,400と700しか使っていないことがわかるので,それ以外を削除しました.
当該コミット:904fc05
font-displayをblockからswapに
font-displayがblock
だとフォントの読み込み完了まで文字が表示されません.swap
と設定し,ローカルにある代替フォントを表示しました.
当該コミット:a67b493
scriptの非同期読み込み
deferで非同期読み込みしつつ,loadイベントを削除しました.
JavaScript
投稿データのキャッシュ
毎回全データをリクエストしていたので,データのキャッシュとlimitの指定をしました.
当該コミット:29f733a
ページ最下部かのチェックを1回だけに
devtoolのパフォーマンスを確認しつつ重たいコードを修正します.念の為 2の18乗回チェックする
というすごいコードだったので,1回だけに変更しました.
当該コミット:8eaceef
画像のトリミングをCSSに置き換え
JavaScriptによる大きさ指定ではなく,object-cover
を使ってトリミングしました.
当該コミット: 347d94f
コンポーネントの大きさ指定をCSSで行う
初期実装では,AspectRatioBox
の大きさ指定がJavaScriptで行われています.CSSのaspectRatio
を使って実装するよう修正しました.
当該コミット:54f9847
音の波形を事前計算しておく
波形計算にかなりCPUを使っていました.描画まで時間がかかる点でも厳しいので,バックエンドで事前に計算しておくようにしました.
当該コミット:7e4a7d7
f69ab0c
bda79f7
規約ページの後半を遅延ロード
本当はReact.lazyを使うつもりだったのですが,うまくいかなかったのでとりあえずフォントだけ遅延ロードするようにしました.
当該コミット:744dd56
バックエンド
babel-nodeではなくnodeを使う
初期実装では,実行時に動的に変換して動作する babel-node を使っていました.事前にトランスパイルしておき,実行には通常のNodeを使うよう修正しました.
当該コミット:1c65c57
brotli圧縮
後にCDN側で解決できるのですが,brotli圧縮をしてファイルを配信しました.詳しくは理解していないのですが,brotli圧縮のほうがgzipより圧縮率が高いようです.
静的ファイルを毎回圧縮するのは非効率なので,brotli-webpack-pluginを使って事前に圧縮しています.
当該コミット: 5e24f5e
CDN(Cloudflare)の導入
Cloudflareを導入して,http2配信やbrotli圧縮,CDNキャッシュ,クライアントキャッシュ設定などを行いました.Expressによる静的ファイル配信がかなり遅いので,効果は大きかったです.
もっとできそうなこと
効果のほどはわかりませんが,できることはまだまだある気がしています.面白いけど,なかなかに難しいですね……