画像ファイルを一括で圧縮する

# 背景・目的

業務中に大量の画像ファイルを一括で圧縮する必要があり、Webpack とプラグインを使って圧縮しました。
画像のフォーマットがおかしいものがあると webpack コマンドが終了せず、調査に時間がかかってしまったため備忘録として解消方法を残します。

# 前提条件

| ライブラリ  | バージョン |
| ----------- | ---------- |
| node        | v16.6.1    |
| npm         | 7.24.2     |
| webpack     | 5.70.0     |
| webpack-cli | 4.9.2      |

## Webpack のインストール・初期設定

以下コマンドを実行し、Webpack をインストールします。
init コマンドを実行すると色々聞かれますが、環境に合わせて適宜設定してください。
単純に画像を圧縮するだけの環境構築であれば、ほとんどの項目で`none`、`No`を選べば良いと思います。

```
$ npm i -D webpack webpack-cli
$ npx webpack init
```

## プラグインのインストール

Webpack に画像圧縮のためのプラグインを追加します。
今回は、GIF と SVG も対応の必要があったため、`squoosh`と`imagemin`を両方使用しました。
（`squoosh`は`.jpg, .jpeg, .png, .webp, .avif`に対応しているようです。）

https://github.com/webpack-contrib/image-minimizer-webpack-plugin

```
$ npm i -D copy-webpack-plugin image-minimizer-webpack-plugin @squoosh/lib imagemin imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo
```

## Webpack の設定を修正

プラグインをインストールしたら、`webpack.comfig.js`を以下のように書き換えます。
今回は`.gif, .svg, .jpg, .png`の画像を圧縮します。

```js
const { extendDefaultPlugins } = require("svgo");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const path = require("path");

const isProduction = process.env.NODE_ENV == "production";

const config = {
  entry: "./src/index.js",
  output: {
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, "src/images"), // 元画像ファイルの置き場所
          to: path.resolve(__dirname, "dist/images"), // 圧縮後のファイルの置き場所
        },
      ],
    }),
    new ImageMinimizerPlugin({
      test: /\.(gif|svg)$/i,
      minimizer: {
        implementation: ImageMinimizerPlugin.imageminMinify,
        options: {
          plugins: [
            ["gifsicle", { interlaced: true }],
            [
              "svgo",
              {
                plugins: extendDefaultPlugins([
                  {
                    name: "removeViewBox",
                    active: false,
                  },
                  {
                    name: "addAttributesToSVGElement",
                    params: {
                      attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
                    },
                  },
                ]),
              },
            ],
          ],
        },
      },
    }),
    new ImageMinimizerPlugin({
      test: /\.(png|jpe?g)$/i,
      minimizer: {
        implementation: ImageMinimizerPlugin.squooshMinify,
        options: {
          encodeOptions: {
            mozjpeg: {
              quality: 85,
            },
            oxipng: {
              level: 3,
              interlace: false,
            },
          },
        },
      },
    }),
  ],
};

module.exports = () => {
  if (isProduction) {
    config.mode = "production";
  } else {
    config.mode = "development";
  }
  return config;
};
```

## 実行（失敗）

`webpack init`コマンドを使用した場合は、npm script に webpack のコマンドが書かれていますので、以下コマンドを実行します。

```
$ npm run build
```

実行すると以下のようなエラーが表示され、いつまでたってもコマンドが終了しないことがあります。
画像ファイルのフォーマットがおかしいなどの理由で、圧縮が実行されません。

```
Premature end of JPEG file
```

```
Unsupported color conversion request
```

```
Corrupt JPEG data: 6469 extraneous bytes before marker 0xd9
```

### ファイルの中からフォーマットがおかしいものを探す

ImageMagic の`identify`コマンドを使うと、ファイルのフォーマットを調べられます。
brew でインストールできます。

```
// ImageMagicをインストール
$ brew install imagemagick

// 画像ファイルを一括で調査
$ ls | xargs -I% identify %
```

**正常なケース**

以下のような出力がされます。
ファイルの拡張子と形式があっているかや、sRGB になっているかなどを確認します。

```
hogehoge1.gif GIF 123x123 123x123+0+0 8-bit sRGB 256c 12345B 0.000u 0:00.001
hogehoge2.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.003
hogehoge3.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
hogehoge4.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
hogehoge5.png PNG 123x123 123x123+0+0 8-bit sRGB 235c 12345B 0.000u 0:00.001
hogehoge6.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
hogehoge7.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
hogehoge8.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
hogehoge9.jpg JPEG 123x123 123x123+0+0 8-bit sRGB 12345B 0.000u 0:00.001
```

**フォーマットがおかしいケース**

```
// CMYK
hogehoge1.jpg JPEG 123x123 123x123+0+0 8-bit CMYK 1234B 0.000u 0:00.007
// グレースケール
hogehoge2.jpg JPEG 123x123 123x123+0+0 8-bit Gray 256c 1234B 0.000u 0:00.001
// 拡張子がjpgだが中身がpng
hogehoge3.jpg PNG 123x123 123x123+0+0 8-bit sRGB 1234B 0.000u 0:00.001
// 破損ファイル
identify: insufficient image data in file `hogehoge4.jpg' @ error/jpeg.c/ReadJPEGImage_/1109.
```

※CMYK やグレースケールはフォーマットがおかしいわけではありませんが、圧縮コマンドの方は対応していないようです。

## 実行（成功）

上記コマンドで失敗の要因となる画像たちを、mv コマンドなどで一旦退避します。
その後、改めて以下コマンドを実行。

```
$ npm run build
```

いくつか WARNING が出るかもしれませんが、webpack 5.70.0 compiled のようなメッセージが出ていれば成功です。