フロントエンドエキスパートチーム内でスピードハッカソンを開催しました

フロントエンドエキスパートチーム内でスピードハッカソンを行いました。通常のスピードハッカソンは共通の題材に対してチームごとにスコアを競うというイメージですが、今回はランダムに決めたチームでそれぞれのチームごとに題材を探すところからはじめ、計測方法も自由としました。

今回のハッカソンは、チームのコミュニケーション促進を主な目的として開催しましたが、結果としてプロダクトの改善につながる成果、とっかかりとなる知見などが得られました。

今回取り組んだ内容などを紹介します。

開催目的とチーム分け

フロントエンドエキスパートチームは現在 9 名いますが、メンバーごとに異なるプロダクトや関心事を業務としています。そのため、同じチームに在籍していながら接点が少ないメンバー同士も存在しています。

普段接点が少ないメンバー同士でチームを組んで、コミュケーションを取りながら一つのことに取り組める機会としてハッカソンが適していると考えて、今回スピードハッカソンの開催に至りました。

チーム分けは次のようになりました。

スピード王 🚀

チームスピード王(@__sakito__, @shisama_, @b4h0_c4t)です。

私達は React + TypeScript で構成されている Web アプリケーションフロントエンドのバンドルサイズ軽量化及び Lighthouse においてのスコア改善を実施しました。

バンドルサイズ軽量化

はじめに、 webpack-bundle-analyzer を用いてアプリケーションに含まれるモジュールが占める割合を可視化しました。

webpack v5 から webpack-cli に標準で webpack-bundle-analyzer が組み込まれたことと、今後の開発体験向上のために webpack v5 へのアップデートを視野に入れて調査しましたが、 webpack v5 で storybook が動作しない挙動に当たってしまい、今回は webpack のアップデートを断念して、webpack-bundle-analyzer を別途インストールして利用することにしました。

f:id:cybozuinsideout:20210225110250p:plain
開始時点での webpack-bundle-analyzer 実行結果

初回の webpack-bundle-analyzer の実行結果では、 Moment.js の locale が全て読み込まれており、かなりファットな状態になっていました。 GoogleChromeLabs でも利用しない locale は削除することが推奨されているため、必要のない locale は webpack-plugin 等を用いて削除するのがよいでしょう。 今回は us locale 以外は全く使用されていなかったことから、それなりにバンドルサイズの軽量化が見込めました。

Moment.js の locale を削除する手法以外にも、 Moment.js を別のパッケージへ移行する手法もあります。 現在、 Moment.js はセキュリティパッチのみのアップデートで、他のパッケージへの移行が推奨されています。

今回次のような理由から Day.js への移行を実施することにしました。

  • インターフェースが Moment.js と似ているため置き換えのコストが低い
  • ファイルサイズが軽量

今回対象としたプロダクトが、Moment.js の機能をそれほど利用していなかったのも以降を決めた理由として挙げられます。

Day.js に置き換えたあとに再度バンドルサイズを比較したところ、約 300KB ほど軽量化できました。

f:id:cybozuinsideout:20210225110254p:plain
Day.js へ移行後の webpack-bundle-analyzer 実行結果

Moment.js 以外の気になる点として、本来本番ビルドに必要のない redux-logger がバンドルに含まれているのに気づきました。

これはロガーの読み込み処理を開発モードかどうかに関わらずimportで行っていたため、バンドルに含まれるようになっていました。これを開発モードかどうか判定する処理のあとにrequire で読み込むことで解決しました。

変更前のコードは次のようになります。

import { createLogger } from "redux-logger";
// ...省略
if (process.env.NODE_ENV !== "production") {
  //use create-logger
}

これを次のように変更しました。

if (process.env.NODE_ENV !== "production") {
  const { createLogger } = require("redux-logger");
  //use create-logger
}

この変更によって redux-logger は本番ビルドには必要ないコードとして認識されるようになり、ビルド時のデッドコードエリミネーションによってバンドルに含まれないようになります。

変更を加えた結果、webpack-bundle-analyzer の出力は次のようになりました。

f:id:cybozuinsideout:20210225110300p:plain
redux-logger削除後の webpack-bundle-analyzer 実行結果

最終的に、プロダクト内 2 つのバンドルファイルにおいて、それぞれ 300-400KB 程度の軽量化に成功しました。

Lighthouse のスコア改善

レイアウトシフトの改善のため、どのような画像が利用されているのか自明な DOM に対してデフォルトの width と height を設定する対応をしました。細々とした作業ではありましたが、レイアウトシフトはフロントエンドのパフォーマンスにおいて重要な指標の一つです。

この対応で、Lighthouse のパフォーマンス項目において全ての要素がグリーンとなりました。

パフォーマンスについては元々のスコアが高かったですが、そこから少しでもスコアを向上させることができたため十分な成果と感じています。

f:id:cybozuinsideout:20210225092104p:plain
初期の Lighthouse スコア

f:id:cybozuinsideout:20210225092109p:plain
レイアウトシフト対応後の Lighthouse スコア

チーム:坂東太郎 🍜

チーム坂東太郎(@koba04, @__sosukesuzuki, @nakajmg)です。

私達のチームは、kintone のポータル画面のレイアウトシフトの改善に取り組みました。

レイアウトシフトが起きているのはポータルのカスタマイズで埋め込んでいる widget エリアと、スペースとアプリの一覧表示エリアです。いずれのエリアもページアクセス時に非同期で表示するコンテンツのデータを取得して、その後レンダリングするようになっています。データ取得の完了順やレンダリングによってレイアウトシフトが起きている状況です。

ぞれぞれのエリアのレンダリングまわりのコードを読み、原因と改善できそうな箇所を探しました。

改善策の検討と実施

レイアウトシフトに対して取れる基本的な手段として、コンテンツをレンダリングするエリアの幅と高さを事前に確保しておくことが挙げられます。

widget エリアやスペース一覧とアプリ一覧のいずれも、初期表示する最大件数がレンダリングする関数に引数として渡されていました。この最大件数をもとにコンテンツに必要なエリアを算出し、一時的に高さを確保しておく対応を行いました。

widget エリアは件数分の高さを確保するように、スペース一覧とアプリ一覧には Facebook で見られるようなコンテンツプレースホルダーの表示を試してみました。

f:id:cybozuinsideout:20210225092114p:plain
コンテンツプレースホルダー

これらの対応の結果レイアウトシフトが軽減され、最初に表示されていたコンテンツが下部に消えるような状態が改善できました。

変更前と変更後で Lighthouse でのスコア計測も行いましたが、非同期処理のタイミングからなのか、レイアウトシフトについてうまく評価できていないようでした。そのためスコア的には変化がありませんでした。スコアには変化が見られませんでしたが、体感としてページ表示の際のガタツキが大幅に軽減できていたので効果は出ていると感じました。

今回行った改善はポータルページのことだけを考えた場当たり的な対応でしたので、プロダクトにコードを反映するにはまだまだ検討する項目がありそうでした。しかしながら内部のコードをしっかりと読むことで、該当箇所の理解が深まったので、これを足がかりとして改善に繋げられそうです。

チーム:とまつオールスターズ 🍻

とまつオールスターズ(@toshi__toma, @pirosikick, @zaki___yama)です

私達のチームは、kintone のモバイルページのパフォーマンス改善に取り組みました。

Lighthouse でモバイルページのスコアを計測した結果から、次の箇所を改善できそうでした。

  • 画像周りの最適化
  • JavaScript の最適化
  • レイアウトシフトの改善

計測は Chrome の DevTools でモバイルの表示をエミュレートして行いました。

画像周りの最適化

ページ内の画像をすべて読み込むようになっていたので、loading="lazy"をつけて遅延読み込みするようにしました。合わせて、表示サイズが決まっている画像に width と height を指定して、レイアウトシフトが起きないようにしました。

loading="lazy"はまだ Safari などのモバイルブラウザで正式にサポートされていませんが、今後サポートが進めば簡単に画像の遅延読み込みを可能にできるので期待しています。

JavaScript の最適化

ファーストビューに関係のない DOM のレンダリング処理が多く行われていたので、レンダリングを遅延させるようにしました。また、お知らせのレコード一覧では非同期なデータ取得とそのレンダリングによってレイアウトシフトが起こっていたので、表示エリアの高さをあらかじめ確保するようにして、レイアウトシフトが起こらないようにしました。

そのほか、複数の非同期処理の並列化や JavaScript の遅延ロードができないかと試みましたが、時間内では成果を得られませんでした。

改善の結果

これらの対応を行った結果、Lighthouse でのスコアが 20 ほど向上しました。

f:id:cybozuinsideout:20210225110650p:plain
対応前(左)と対応後(右)のスコア

これらの対応はまだプロダクトへと反映できていませんが、徐々に改善を進めていければと思います。

Chrome の Local Overrides が便利

今回、ローカル環境で実際に使われているのと同じようなデータを事前に用意できなかったこともあり、計測と改善を Chrome のLocal Overridesを使って行いました。

プロダクトの本番環境に適用せずに手元で変更したコードが反映できるので、短い時間で変更と計測を繰り返せました。オススメ機能ですので、まだ試したことがないのであればぜひ一度試してみてください。

おわりに

フロントエンドエキスパートチーム内で開催したスピードハッカソンついての紹介でした。

今回、普段あまり一緒にモブなどをする機会の少ないメンバー同士がチームとしてワイワイ作業ができました。どのチームもプロダクトを題材に選んだことで、プロダクトのコードへの理解や、実際に改善に取り組む際の障壁や課題の理解の助けになりました。

お試しで実施してみたハッカソンでしたが、チームにとってもプロダクトにとっても得られるものが多くあったので、今後もテーマを変えて継続的に開催していきたいと思います。