EagleLand

ワークフローにおける画像の最適化

Published at 2016-07-26

画像の最適化をビルドプロセスでやるのか、コミット前にやるのかという話。

ビルド時の画像の最適化

ビルドプロセスに画像の最適化を行うことはしばしばある。 Gulp や Grunt のプラグインとしては sindresorhus/gulp-imagemingruntjs/grunt-contrib-imagemin があったり、追加プラグインのインストールやオプションの設定が面倒な人のために、拙作の gulp-imagegrunt-image などもある。その他の CSS や JavaScript といったアセットのビルドと併せて、こうしたツールを実行するのが一般的である。

👍 Pros

👎 Cons

処理にかかる時間が長くなるというのは中々に厄介で、リリース作業の長時間化だけでなくローカル開発時の効率にも関わる問題で、中長期的な生産性に関わる恐れもある(やや大げさかもしれないが)。

バージョン管理時の画像の最適化

他の解決策としては、バージョン管理に含める段階で最適化をしておくというものがある。つまり、 JPEG ・ PNG ・ GIF ・ SVG ・ WebP を処理した上でgit commitして、デプロイ時には行わないというものだ。

👍 Pros

👎 Cons

コミットに含まれる画像を git の pre-commit で最適化する

バージョン管理する段階で実施すると問題のある程度が解決するので、何か方法はないかと思っていたところ、 git の pre-commit でやったらいいよねという話になる。 pre-commit で画像を最適化するサンプルを編集長が教えてくれたので、それを改変したのがこちら。

#!/bin/sh
# to use, save this file as .git/hooks/pre-commit in your git repo
# make sure to add execute permissions using: chmod +x .git/hooks/pre-commit
command -v imgo >/dev/null 2>&1 || {
  echo "\033[1mPlease install imgo to reduce images size before commit\033[0m"
  echo "Install imgo with the following:"
  echo "\t \033[1mnpm install -g imgo\033[0m"
  exit 1;
}

for file in `git diff --cached --name-status | awk '$1 ~ /[AM]/ && tolower($2) ~ /\.png$/ {print $2}'`
do
echo "Optimizing $file"
  cat $file | imgo --pngquant --zopflipng > ${file%.png}.new
  mv -f ${file%.png}.new $file
  git add $file
done

for file in `git diff --cached --name-status | awk '$1 ~ /[AM]/ && tolower($2) ~ /\.jpe?g$/ {print $2}'`
do
echo "Optimizing $file"
  cat $file | imgo --jpegoptim --jpegRecompress --mozjpeg > ${file%.jpg}.new
  mv -f ${file%.jpg}.new $file
  git add $file
done

for file in `git diff --cached --name-status | awk '$1 ~ /[AM]/ && tolower($2) ~ /\.gif$/ {print $2}'`
do
echo "Optimizing $file"
  cat $file | imgo --gifsicle > ${file%.gif}.new
  mv -f ${file%.gif}.new $file
  git add $file
done

for file in `git diff --cached --name-status | awk '$1 ~ /[AM]/ && tolower($2) ~ /\.svg$/ {print $2}'`
do
echo "Optimizing $file"
  cat $file | imgo --svgo > ${file%.svg}.new
  mv -f ${file%.svg}.new $file
  git add $file
done

元ネタでは pngcrushjpegtran のみで処理しているが、改変バージョンでは自作の imgo というツールを通すようにし、 JPEG と PNG に加えて GIF と SVG も最適化するようにしてある。 imgonpm install -g imgo でインストールできる。

この pre-commit を対象プロジェクトの .git/hooks に配置しておけば git commit しようとした時に、自動で最適化して git add しなおしてくれる。

バージョン管理時とビルド時の両方に実施する?

両方のメリットを享受したいところだが、単に両方で実施すると、

  • 画像それぞれの特徴に併せた細やかな最適化処理を施せない
  • 対象の画像が多くなると処理にかかる時間が長くなる

の問題が残る。

対象の画像が最適化されているかどうか をチェックできれば、未実施の画像ファイルに対してだけ処理すれば良い。が、現実解が見当たらない。チャンクや更新日時のチェックなども考えられるが、それが全ケースで完璧に機能してくれるのかどうかに自信がない。

ビルド時の実施はセーフティネットとしても有効なので、これらの問題を解決できればバージョン管理時・ビルド時の両実施が望ましい形だと思われる。

オンザフライで最適化する?(おまけ)

画像へのリクエスト時に最適化してからクライアントに返すという発想もある。実現するソリューションとして、弊社では hayabusa というものがある。

画像サーバーのプロキシとして動作し、画像の URL にクエリパラメータを付与すると応じて最適化された画像が返却されるという仕組み。最適化処理はその URL へのリクエストのうち初回のみ実施されてキャッシュされるので、最適化にかかるコストは無視できる程度である。うっかりクエリを付け忘れなければ、いつでもどこでも適切に処理された画像をダウンロードできるというわけだ。

大掛かりな仕組みではあるので、導入は比較的困難である。多くの Web 制作現場では、こうしたツールの導入は難しいだろう。

hayabusa のコンセプトはやや富豪的にも感じていて、アップロード時にきっちり処理してあげれば、それが良いのではと思っている。 hayabusa のようにリクエスト時に処理を行うのとは逆に、サーバーへのアップロード時に自動で最適化してはどうだろうということで、 AWS Lambda で動作する ysugimoto/aws-lambda-image というツールがある。

リサイズやクロップが必要なケースは?という話では、きっちり最適化した一枚を扱う方が効率よいケースもあると思っている。例えば、大きな画像とそれをリサイズしたサムネイルの2枚をダウンロードするより、大きな画像をダウンロードすればそれだけで済む。オンメモリでリサイズするほうがトータルコストが安そう?だし、サムネイルとしてダウンロードすればそのまま大きな画像のキャッシュになるので無駄にならない。クロップに関してはCSSでやるほうが筋だと思う。とは言え、大きな画像とサムネイルのサイズに差異が大きければドデカイ画像をサムネイルとしてダウンロードする羽目になるので、結局は時と場合に依ってしまう。

ぶっちゃけ hayabusa のような仕組みはとても便利なので、使えるならそれが一番楽。

タイトルと URL をコピーしました