[Git] コミット時に画像ファイルを最適化する方法

背景

非エンジニアから提供される画像は最適化されていないものが多くある。それをそのままプロダクションに公開すると、画像のサイズが大きくなりページの読み込みに影響を与える。そのため、提供されてた画像を個別で最適化して管理していくことになる(CDN で画像最適化が出来ないものとする)。

Git 管理(コミット)時に画像の最適化を自動化したい。

方法

  1. コミット時に画像ファイルがあるか
    husky + lint-staged を利用して pre-commit を設定する
  2. 対象の画像ファイルがあれば最適化を行う
    sharp を利用して画像最適化を行う
  3. 対処ファイルをコミットする

パッケージの準備

パッケージのインストールを行う。

npm i -D husky lint-staged sharp

それぞれの用途で追加する。

husky の設定

設定方法は環境によって異なるが、以下のような設定をした。基本的には husky のインストール時のスクリプトによって自動的に設定されるため、lint-staged の対象拡張子の指定をしておけば良い。

.husky/pre-commit:

npx husky add .husky/pre-commit "npx lint-staged" を実行して追加すれば良い。

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged

package.json:

  "lint-staged": {
    "**/*.{png,jpeg,jpg,gif}": "node compress-image.js"
  }

実装

pre-commit 時に渡されたファイルパスを最適化するスクリプトは下記の通り。

// compress-image.js
const fs = require("fs");
const path = require("path");
const sharp = require("sharp");

const changeFormat = (image, extname) => {
  switch (extname) {
    case ".jpeg":
    case ".jpg":
      return image.jpeg({ quality: 85 });
    case ".png":
      return image.png({ quality: 100 });
    default:
      return image;
  }
};

const minifyFile = (filename) => {
  new Promise((resolve, reject) => {
    fs.readFile(filename, function (err, sourceData) {
      if (err) throw err;

      const extname = path.extname(filename).toLowerCase();

      changeFormat(sharp(sourceData), extname).toFile(filename, (err) => {
        err ? reject(err) : resolve();
      });
    });
  });
};

Promise.resolve(process.argv)
  .then((x) => x.slice(2))
  .then((x) => x.map(minifyFile))
  .then((x) => Promise.all(x))
  .catch((e) => {
    console.error(e);
    process.exit(1);
  });

ファイル形式によって最適化の設定が異なるので、拡張子によって sharp に渡すパラメータを出し分けしている。