今年も早いものでアドベントカレンダーの季節ですね。
勢いで12月序盤の記事投稿を予約したものの、全くネタを思いつかなかったので、
苦肉の策で以前に社内で行ったスピードハッカソンの技術的な振り返り記事を書くことにしました。
以前といっても1年以上前の話なのですが、、振り返りはしたいと思っていたのでちょうどいいはず。。
イベントの詳細はこちらの記事を御覧ください。
ニジボックスのエンジニアがスピードハッカソン開催!「パフォーマンス改善で大事なのは、現状を知ること」 | NIJIBOXのフロントエンドエンジニア
Lighthouseでパフォーマンス計測
Lighthouseとは
Webページの品質向上に役立つよう開発されたオープンソースのツールです。
以下の画像のようにGoogle ChromeのDevToolsのLighthouseタブを選択して、「Generate reportボタン」を押すだけでサイトのパフォーマンス、アクセシビリティ、ベストプラクティス、SEO、PWA の対応状況について確認することができます。
Lighthouseを使用する際は、以下2点に注意してください。
- シークレットモードを使う
- Lighthouseの設定「Clear storage」をONにしておく
シークレットモードを使う理由は、Chromeの拡張機能がパフォーマンス計測に影響する可能性があるためです。(なので拡張機能をすべて無効にできた状態であれば通常のブラウザでも大丈夫です)
「Clear storage」はLighthouseタブ右上のsettingsボタンクリックから表示できるチェックボックスです。
デフォルトではONになっているので問題ないですが、OFFの状態だとブラウザキャッシュやLocal Storageなどの影響でスコアが高く出てしまうため注意してください。
そして今回のスピードハッカソンでは、LighthouseのPerformanceのスコアをどこまで高められるかを競いました。
対応前のスコアがこちらで、
以下記事の内容を対応後のスコアがこちらになります。
実際の競技内ではこんなにスコアを上げられなかったのですが、競技の振り返り解説の内容を踏まえてスコアを100点にできたので、その実装した内容を説明します。
Image
まず始めに画像の容量が大きすぎる警告が出ていたため、画像の調整を行いました。
Properly size images
Serve images that are appropriately-sized to save cellular data and improve load time.
画像圧縮
pngquantは、CLI上でpng画像を圧縮できるツールです。
Homebrewなどでインストールした後に以下のコマンドで使用することができます。
$ brew install pngquant
$ pngquant --ext .png --force --speed 1 *.png
--ext .png --force
は圧縮後の画像を元画像に上書きして生成するオプションです。
さらに--speed 1
をつけることで圧縮効率最大で圧縮できます。
参考 : png画像の圧縮には pngquant を使おう [Mac/Win] - Qiita
Webpに変換
JPEGやPNGの画像を、さらに圧縮率の高い画像フォーマットWebPに変換します。
参考 : 次世代画像形式のWebP、そしてAVIFへ。変わり続ける技術に対応するweb制作の黄金解 - ICS MEDIA
$ brew install webp
$ for file in *.{jpg,png}; do cwebp $file -o ${file%.*}.webp; done
変換にはcwebpを使いました。
Linuxコマンドのfor文でループさせて、指定ディレクトリ内のすべてのjpgとpng拡張子画像をWebPフォーマットに変換しています。
変換後は画像読み込み記述のjpg、png拡張子もwebpに置き換えることを忘れずに。
参考 : Macコマンドで簡単!PNG&JPGをWebPに一括変換 | Cruw
画像リサイズ
画像のサイズ自体が大きすぎる場合は、画像サイズを縮小する必要があります。
ここではCLI上で画像操作をするためにImageMagickを使用しました。
$ brew install imagemagick
$ convert image.webp -resize 50% image.webp
image.webpという名前の画像を大きさ50%にresizeしています。
遅延読み込み
Defer offscreen images
の警告が出ている場合は、画像の遅延読み込み設定をしましょう。
設定は簡単で、imgタグのloading属性にloading="lazy"
を記述するだけです。
<img src="image.jpg" alt="..." loading="lazy">
参考 : Lazy loading - Web Performance | MDN
Font
Webフォントなどのフォントファイルは容量が大きく、パフォーマンス影響が出やすい部分です。
使用していないWebフォントのダウンロードをやめて、必要な分だけを読み込むようにしましょう。
CSSファイルで読み込んでいる場合
例えばこのようにCSSファイルでAdobe Fontsを読み込んでいる場合、
@import url("https://use.typekit.net/mqn2xnp.css");
読み込み先のurlにアクセスして、必要なfont-weightのみをimportするようにします。
/* この行を削除 */
@import url("https://use.typekit.net/mqn2xnp.css");
/* 以下を追記 */
/* font-weight:700のフォントのみを読み込む */
@font-face {
font-family:"soleil";
src:url("https://use.typekit.net/af/1f781f/00000000000000003b9aef83/27/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n7&v=3") format("woff2");
font-display:auto;
font-style:normal;
font-weight:700;
}
対象ブラウザがChromeだけであれば読み込むフォントフォーマットはwoff2形式だけで大丈夫です。
参考 : WOFF (Web Open Font Format) - 開発者ガイド | MDN
さらに、Webフォントデータをローカルにダウンロードして自前サーバに配置することで、さらなるパフォーマンス向上も期待できます。その場合はフォントのライセンスを確認して、自由に使えるフォントかどうか注意してください。
@font-face {
font-family:"soleil";
src:url("../font/soleil_700.woff2") format("woff2"); /* フォントをローカルに落としてきた */
font-display:auto;
font-style:normal;
font-weight:700;
}
HTMLから読み込む場合
例えばHTMLファイルからGoogle Fontsを読み込む場合、
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
このようにpreconnectキーワードをつけることで、指定したドメインへの接続(DNSルックアップ、TCPハンドシェイクもしくはTLSネゴシエーション)を事前に行うことができます。
preconnectはIEやFireFoxでは対応していないので、それらにも対応させたい場合はdns-prefetchを使用してください。
参考 : Can I use preconnect
またcrossorigin属性をつけることも必須です。(詳細)
font-display: swapについて
Webフォントは外部からフォントデータをダウンロードする関係上、どうしても画面表示まで時間がかかります。
その場合、Webフォントに対してfont-display: swap
を指定することで、Webフォントがダウンロード完了するまでの間はシステムフォントでテキストを表示することができます。
@font-face {
font-family:"soleil";
src:url("../font/soleil_700.woff2") format("woff2");
font-display:swap; /* swapに変更 */
font-style:normal;
font-weight:700;
}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- &display=swapを追加 -->
<link href="https://fonts.googleapis.com/css2?family=Roboto&display=swap" rel="stylesheet">
フォントの切り替わりのタイミングで画面がちらつくので、より早くテキストを表示させたいときなどに使用してください。
以下Gif画像はfont-display: swap
を指定した場合の画面表示例です。
First Paintの表示とWebフォントダウンロード後の表示で、「Pick Up」のフォントが切り替わっていることが分かると思います。(検証のため、NetWorkの速度を落としています)
Content-Encoding
Enable text compression
の警告が出ている場合は、Webサーバーのテキスト圧縮の機能を有効にする必要があります。
今回はcompressionを使用し、gzip圧縮を行いました。
以下Expressでサーバを立ち上げている場合の実装方法例です。
const express = require('express');
const app = express()
app.use(express.static('public'));
app.listen(3000, () => {
console.log(`Example app listening at http://localhost:3000`)
})
const express = require('express');
// npm install compression後に、express立ち上げファイルでcompressionを読み込む
var compression = require('compression')
const app = express()
// compressionを実行する
app.use(compression());
app.use(express.static('public'));
app.listen(port, () => {
console.log(`Example app listening at http://localhost:3000`)
})
対象ファイルのResponse Headers記述のContent-Encodingの値がgzipになっていれば成功です。
また、さらに高度な圧縮アルゴリズムであるBrotli形式で圧縮する話も出ましたが、2021年12月時点でexpressのBrotli対応PRがmergeされていないため、gzipで圧縮しています。
CSS
Eliminate render-blocking resources
Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles.
レンダリングをブロックしている読み込みファイルが多いとのことなので、ChromeのCoverage機能を利用して不要なファイルや記述をあぶり出していきます。
CoverageタブはDevToolsの検索ウインドウ(「Cmd + Shift + P」で表示)で検索することで表示させることができます。
参考 : Find Unused JavaScript And CSS With The Coverage Tab - Chrome Developers
CSSの削除
Coverageの結果を見ながら、まずは不要なCSSを削除していきます。
各ファイルをクリックすると、Sourceタブにファイル内で使用していないCSS記述を表示してくれるので、不要な記述を削除していきましょう。
ただメディアクエリなどで現在適応されていないCSSもunusedなCSSと表示されることには注意が必要です。
Minify
$ npm install -D cssnano-cli
$ for file in *.css; do npx cssnano < $file > ${file%.*}.min.css; done
CSSをminifyしてくれるモジュールであるcssnanoのcliバージョンを使用しています。
上の例ではfor文でループさせてディレクトリ内のすべてのCSSファイルのminifyしています。(パス指定はディレクトリ構造に合わせて変えてください)
JavaScript
JavaScriptの削除
JavaScriptもCSSと同様にCoverageタブで未使用のファイルや記述を確認し、それらを削除していく手順となります。
Lodashなどのライブラリは、実際に使用する部分以外にも大量の記述やファイルがダウンロードされてしまうため、パフォーマンス悪化に繋がりがちです。
その場合は代替メソッドを手動で定義するなどして、可能な限り使用を控えるようにしましょう。
Lodashであれば、以下GitHubページに各関数の代替記述が載っているので確認してみてください。
GitHub - you-dont-need/You-Dont-Need-Lodash-Underscore: List of JavaScript methods which you can use natively + ESLint Plugin
参考 : lodash やめ方 - Qiita
Minify
Webpackを使用してJSファイルをまとめていきます。
まずは必要パッケージをインストールして、
$ npm install -D webpack webpack-cli
このようにWebpackのconfigファイルを最小限で設定します。
module.exports = {
mode: "production",
entry: `./js/index.js`,
output: {
path: `${__dirname}/js`,
filename: "bundle.js"
}
};
最後にコマンドを実行すればJSファイルが1ファイルにまとまっているはずです。
$ npx webpack
参考 : 最新版で学ぶwebpack 5入門 - JavaScriptのモジュールバンドラ - ICS MEDIA
HTML
HTMLのminifyはパフォーマンスに影響しにくい部分でありますが、流れでご紹介。
html-minifierを使うことでCLI上からminifyできます。
$ npm install -D html-minifier
$ npx html-minifier --collapse-whitespace --remove-comments --remove-optional-tags --remove-redundant-attributes --remove-script-type-attributes --remove-tag-whitespace --use-short-doctype --minify-css true --minify-js true index.html > index.min.html
公式のsample通りのオプションをつけています。各オプションの内容については以下の記事が詳しいです。
参考 : 【備忘録】HTMLMinifierの全オプションについて調査した - Qiita
まとめ
パフォーマンスチューニングはブラウザの仕組みを正しく把握し、ボトルネックになっている箇所に対して適切に改善を加えていくことが大切です。
ここで学んだ内容を引き続き業務に生かしてきたいですね。