パフォーマンスチューニング系の書籍でほぼ必ず出てくるけど..
「gzipって、つまり何者?」
という、フロントエンド向けの記事です。
ファイル圧縮形式gzipとキャッシュ設定Expiresについて、MAMPのApacheサーバで簡易的にテストしてみました。
誤っている情報がありましたら、ご指摘いただけると助かります<(__)>
有効にしたくなった背景
Gulpなどのタスクランナーを利用して、html/css/js/画像といったリソースの最適化を行ったりする開発環境を作っていると、様々なパッケージを利用してコテコテのgulp環境を作りたくなってしまいます。
ふと、 **「クライアントで色々と頑張ってるけど、そもそもサーバサイドの最強と名高いキャッシュやGoogleも比較的ゴリ押しgzipってどうなんだろ?」**と思いました。
聞いた事位はあるけど、サーバーサイドの対策が必要なため調べるのが億劫です。
しかしながら、キャッシュやgzipの魅力を考えると取りあえず仕組みだけでも理解しておいた方が良さそうなので、調べてみる事にしました。
gzipの仕組み
とりあえず、キャッシュの仕組みについては理解しているため、gzipの仕組みを調べました。
下記記事の説明が分かりやすかったです↓
gzip圧縮のしくみ
gzipは、minifyやUglifyなどのフロントエンドにおなじみの圧縮形式とは異なり、画像の圧縮に用いられるような高圧縮な形式で行われるそうです。文章の原型を留めません。
(ざっくり理解ですみません)
かつ、JPEGのように非可逆的(画像が劣化したまま元に戻せない)な圧縮形式ではなく、元に戻せる形式をとるため、「リソースを通信時は圧縮、使うときに展開」といった利用方法が可能になります。
上記のような特性を持つため、利用する方法として一般的なのは下記いずれかのようです。
なお、いずれも **「利用(gzip展開)可能なブラウザのみで読み込まれるようにする設定」**が必要となります。(後述)
- 事前にgzipファイルを準備しておく(タスクランナーやシェルで作成)
- サーバサイドでリクエストを受け取った際、リアルタイムにgzipファイルを生成してクライアントに配布する
後者の場合、フロントエンドの作業が減りますね。
ちなみに、現行のブラウザのほとんどはgzipに対応しているようです。
(ガラケー時代レベルだと非対応のものもあるらしい)
gzipファイルを作成
この記事では、前節で紹介したgzipの利用方法の内、「事前にgzipファイルを準備しておく」という方法を試してみたいと思います。
なお、この記事では下記のようなファイル・ディレクトリ構成を前提として進めていきます。
- app/
- .htaccess # gzipの読み込み設定
- gulpfile.babel.js # gzip作成タスク
- dev/
- js/
- app.js # gzip変換前のファイル
- js/
- prod
- index.html # gzipを読み込むhtml
- js/
- app.js.gz # gzip変換後のファイル
- app.js # 比較用にuglifyしたファイル
gzip変換前のapp.jsには、JQueryなど大きめのライブラリなどを入れておくと、比較する時のファイルサイズ差が顕著になると思います。
また、uglifyしたファイルとも比較したいと思います。(uglifyするタスクの書き方は紹介しません)
下記のようなgulpのタスクをgulp-gzipというパッケージを利用して、記述します。
(gulpパッケージは、本当になんでもありますね)
import gulp from 'gulp';
import gzip from 'gulp-gzip';
gulp.task('jsGzip', () => {
gulp.src('dev/js/app.js')
.pipe(gzip())
.pipe(gulp.dest('prod/js'))
});
app/dev/js/app.js
をapp/prod/js/app.js.gz
に変換するタスクです。
これとは別途、gzip非対応ブラウザ用のファイルもapp/prod/js/app.js
として用意します。
なお、上記はjavascript用のgzipを作成する例ですが、html/css/画像でもgzip形式に圧縮できます。
app/prod/index.html
からは、下記のようにファイルを読み込んで下さい。
<script src="./js/app.js"></script>
普通の読み込み方ですが、後ほどのapp/.htaccess
の設定によって、gzip対応ブラウザは自動的にapp/prod/js/app.js.gz
を読み込むようになります。
下記は、JQuery2系で作成した場合のファイル群のサイズです。
ファイル名 | dev/js/app.js | prod/js/app.js | prod/js/app.js.gz |
---|---|---|---|
圧縮形式 | 非圧縮 | uglify | gzip |
サイズ(KB) | 258 | 88 | 38 |
gzipされたファイルは、uglifyされたファイルから約57%サイズを削れています。
gzipでは同様のパターンが多い文字列である程、圧縮率が高くなるらしいです。
みんな大好きMAMPの設定
この記事ではapp/prod/index.html
を利用するため、まずhtmlを認識するように設定します。
http.conf
の138行目に.html
を追記します。
138 AddType application/x-httpd-php .php .phtml
↓
138 AddType application/x-httpd-php .php .phtml .html
次に、下記のように記述した.htaccess
をアプリケーションルートに配置します。
内容について詳しくは、ページ下部の参考記事を参照して下さい。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME} \.(css|js|html)$
RewriteCond %{REQUEST_FILENAME} !\.gz$
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule .+ %{REQUEST_URI}.gz [L]
<FilesMatch "\.html\.gz$">
ForceType text/html
AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.css\.gz$">
ForceType text/css
AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.js\.gz$">
ForceType application/javascript
AddEncoding x-gzip .gz
</FilesMatch>
Header append Vary Accept-Encoding env=!dont-vary
</IfModule>
# 注意: ファイル末尾に一行空けて下さい (このコメントは削除)
あとは、MAMPの[設定→Webサーバ]から、WebサーバにApacheを選択して、ドキュメントルートをapp
に設定すれば完了です。
gzipファイルを読み込んで(展開して)いるか確認
確認にはGoogleChromeのデベロッパーツールを利用します。
MAMPサーバを起動したら、localhost:8888/prod
というURLを叩きます。(ポート8888はMAMP
初期設定)
この時点でapp/prod/js/app.js.gzip
が読み込まれている状態になります。
確認のため、cmd + opt + i
でデベロッパーツールを開いて、[Network -> JS]の順にタブを選択します。
すると、app.js
のSizeが表示されています。
(ファイルが何も表示されていない場合はタブが選択された状態でリロードして下さい)
若干ファイルサイズに誤差がありますが、gzip後のファイルが読み込まれているのが確認出来ます。
(なお、サイトの表示・動きにも問題ありません)
では、次に.htaccess
内の記述をコメントアウトした場合です。
ugilifyされたファイルが表示されています。
これで、gzipの設定と確認は終わりです。
今回は予めgzipファイルを用意しましたが、規模によってはサーバサイドで自動生成する仕組みを取り入れた方が、賢そうです。
ちなみに、実はapp.css
とindex.html
もgzip化しているので、スクリーンショットを比較してみて下さい。
Expiresを設定
今度は、リソースのキャッシュの有効期限を設定します。
.htaccess
に、Expireの設定を追記します。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{REQUEST_FILENAME} \.(css|js|html)$
RewriteCond %{REQUEST_FILENAME} !\.gz$
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule .+ %{REQUEST_URI}.gz [L]
<FilesMatch "\.html\.gz$">
ForceType text/html
AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.css\.gz$">
ForceType text/css
AddEncoding x-gzip .gz
</FilesMatch>
<FilesMatch "\.js\.gz$">
ForceType application/javascript
AddEncoding x-gzip .gz
</FilesMatch>
Header append Vary Accept-Encoding env=!dont-vary
ExpiresActive On
ExpiresByType image/png "access plus 1 months"
ExpiresByType image/jpeg "access plus 1 months"
ExpiresByType image/gif "access plus 1 months"
ExpiresByType text/html "access plus 1 months"
ExpiresByType text/css "access plus 1 months"
ExpiresByType text/javascript "access plus 1 months"
ExpiresByType application/javascript "access plus 1 months"
</IfModule>
# 注意: ファイル末尾に一行空けて下さい (このコメントは削除)
上記では、png,jpeg,gif,html,css,jsに設定しています。
(記述そのままですが..)
そして、なんとこれで設定完了です。
Expiresキャッシュが効いているか確認
デベロッパーツールを開き、再び[Network -> JS]タブから、app.jsの状態を確認します。
今度は、app.jsをクリックして下さい。
すると、app.jsの[Header -> Response Header]にCache-control: max-age=2592000
という記載があるのが確認出来ます。
この状態が、キャッシュが効いている状態になります。
では、例によって.htaccess
からExpiresの設定を削除して、ブラウザをリロードします。
この際、キャッシュを一旦飛ばすため
Disable cache
にチェックを入れてからリロードして下さい。(下記画像の青枠で囲った部分)
すると[Header -> Response Header]に、先ほどのCache-control: max-age=2592000
の記載がなくなっているのが確認出来ます。
Expires設定がされていないため、キャッシュされていません。
Expires設定の注意点として、リソースのクエリとしてダイジェストを付与するような仕組みを用意していないと **「キャッシュが効いてて更新されない地獄」**に陥る可能性があります。
これで、gzip・Expiresについての検証は終わりです。
終わりに
今回の記事では.htaccess
に設定を記述することで、gzipとExpiresを有効にしましたが、これが現場レベルで使える正しい方法なのかは分かりません<(__)>;
ちなみに、RailsではProduction環境のアセットパイプラインでもgzipファイル生成機能を提供しているようです。
しかし、いずれにしてもサーバサイドの領域になるため、活用する場は個人的な案件、もしくは現場にて「サーバーサイドにお願いする」といったパターンになりそうです。
コンマ1秒のスピードが収益に関係してくるようなサービス、もしくは、もはやタスクランナー改造する位しかやる事無い状態なケースでは、フロントエンドからも積極的に働きかける価値があると思いました。
参考記事
gzip圧縮のしくみ
gzip圧縮でCSSやJSなどの転送量を減らす方法
gzip圧縮転送についてトコトン調べてみた
改行削除するくらいなら gzip したらいいじゃない
(´-`).。oO(minify、uglifyでドヤってましたすいません..)