webpackの画像最適化:image-minimizer-webpack-pluginで配信サイズを削減する

概要

画像はフロントエンドの転送量の大半を占めることが多く、ビルド時に圧縮をかけるだけで配信サイズを大きく減らせます。本記事では webpack 5 で image-minimizer-webpack-plugin(内部で sharp / imagemin を利用)を使い、ビルドパイプラインに画像最適化を組み込む手順をハンズオン形式でまとめます。次世代フォーマット(WebP / AVIF)の生成まで扱います。

> はじめに(重要): image-minimizer-webpack-plugin は現在 deprecated となり、リポジトリはアーカイブ(読み取り専用)に移行しています。開発は後継の minimizer-webpack-plugin に引き継がれ、こちらは画像だけでなく JS / CSS / HTML / JSON も1つのプラグインで最適化・生成できます。既存プロジェクトは 5.x のまま継続利用でき(セキュリティ修正は継続、ただし新機能は後継側のみ)、新規に導入するなら後継の minimizer-webpack-plugin を検討してください。ミニファイ/ジェネレータ名(imageminMinify / sharpMinify など)や optimization.minimizer への組み込み方は後継でもほぼ同じ考え方なので、本記事の設計・測定の考え方はそのまま応用できます。

環境

  • Node.js 20 以降(執筆時点の Active LTS は 24)
  • webpack 5.x
  • image-minimizer-webpack-plugin 5.x 系

> image-minimizer-webpack-plugin は v4 で最低 Node.js 18.12、v5 で最低 Node.js 20.9 に引き上げられました。Node 18 はすでに EOL のため、5.x を使う場合は Node 20 以上(できれば LTS の 24)を用意してください。 > > なお、古い記事では imagemin-webpack-plugin(EngineHub 製)が紹介されていることがありますが、webpack 5 では公式メンテの image-minimizer-webpack-plugin(およびその後継)を使うのが現在の推奨です。本記事はこちらを前提にします。

発生した問題

ビルド後の dist/ を確認すると、画像が最適化されないまま出力されていました。

  • デザインツールから書き出した PNG がそのまま同梱され、1枚で数百 KB
  • JPEG の品質が 100 のままで、視覚的にはほぼ差がないのにサイズが大きい
  • 次世代フォーマット(WebP / AVIF)を一切配信しておらず、対応ブラウザでも恩恵を受けられない

結果として初回表示の転送量が膨らみ、LCP(Largest Contentful Paint)が悪化していました。

原因

webpack は Asset Modules(asset/resource など)で画像を出力ディレクトリにコピーするだけで、デフォルトでは圧縮を行いません。つまり「ソースの画像サイズ = 配信サイズ」になります。最適化を効かせるには、画像を Asset Modules で扱ったうえで、ミニファイ処理を明示的に挟む必要があります。

対応方法

方針は次の3段階です。

  1. 画像を Asset Modules(asset / asset/resource)で読み込む
  2. image-minimizer-webpack-plugin で本番ビルド時に圧縮する
  3. 必要に応じて WebP / AVIF を追加生成する

インストールの選択肢は2通りです。新規導入なら、メンテ状況・Apple Silicon を含むクロスプラットフォーム対応の観点から sharp ベースを第一候補にするのがおすすめです。 sharp は JPEG / PNG / WebP / AVIF / GIF を扱えます(SVG は扱えないため、SVG を最適化したい場合のみ imagemin-svgo を併用します)。

# 新規導入向け(推奨): sharp ベース
npm i -D image-minimizer-webpack-plugin sharp
# SVG も最適化する場合のみ追加
npm i -D imagemin imagemin-svgo

既存プロジェクトで imagemin 構成を引き継ぐ場合は、従来どおりフォーマットごとに個別パッケージを入れます。

# 従来資産向け: imagemin ベース
npm i -D image-minimizer-webpack-plugin \
  imagemin \
  imagemin-mozjpeg \
  imagemin-pngquant \
  imagemin-gifsicle \
  imagemin-svgo

実装例

1. Asset Modules で画像を扱う

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: "asset", // 8KB未満はインライン、それ以上はファイル出力
        parser: {
          dataUrlCondition: { maxSize: 8 * 1024 },
        },
        generator: {
          filename: "images/[name].[hash:8][ext]",
        },
      },
    ],
  },
};

2. 画像を圧縮する(本番のみ)

ImageMinimizerPlugin を optimization.minimizer に追加します。minimizer に置くことで、JS の minify と同じ最適化フェーズで実行されます。この組み込み方("..." のスプレッド+minimizer オプション)は 5.x でも変わっていません。

新規導入向け(sharp ベース・推奨)

// webpack.config.js
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const isProduction = process.env.NODE_ENV === "production";

module.exports = {
  mode: isProduction ? "production" : "development",
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: "asset",
        parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
        generator: { filename: "images/[name].[hash:8][ext]" },
      },
    ],
  },
  optimization: {
    minimizer: isProduction
      ? [
          "...", // JS minifier(terser)を維持するためのスプレッド
          new ImageMinimizerPlugin({
            minimizer: {
              implementation: ImageMinimizerPlugin.sharpMinify,
              options: {
                encodeOptions: {
                  jpeg: { quality: 80, progressive: true },
                  png: { quality: 80 },
                  webp: { quality: 80 },
                  // avif: { quality: 50 }, など必要に応じて
                },
              },
            },
          }),
        ]
      : [],
  },
};

sharp は SVG を扱えないため、SVG も最適化したい場合は imagemin-svgo を使う minimizer を追加で組み合わせます(後述の imagemin 構成の svgo 設定がそのまま使えます)。

従来資産向け(imagemin ベース)

既存プロジェクトで imagemin 構成を維持する場合は、従来どおり imageminMinify を使います。

// webpack.config.js
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const isProduction = process.env.NODE_ENV === "production";

module.exports = {
  mode: isProduction ? "production" : "development",
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        type: "asset",
        parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
        generator: { filename: "images/[name].[hash:8][ext]" },
      },
    ],
  },
  optimization: {
    minimizer: isProduction
      ? [
          "...", // JS minifier(terser)を維持するためのスプレッド
          new ImageMinimizerPlugin({
            minimizer: {
              implementation: ImageMinimizerPlugin.imageminMinify,
              options: {
                plugins: [
                  ["mozjpeg", { quality: 80, progressive: true }],
                  ["pngquant", { quality: [0.65, 0.9] }],
                  ["gifsicle", { interlaced: true, optimizationLevel: 2 }],
                  [
                    "svgo",
                    {
                      plugins: [
                        {
                          name: "preset-default",
                          params: {
                            overrides: { removeViewBox: false },
                          },
                        },
                      ],
                    },
                  ],
                ],
              },
            },
          }),
        ]
      : [],
  },
};

ポイントは次の通りです。

  • optimization.minimizer に "..."(文字列のスプレッド)を入れることで、webpack デフォルトの JS minifier(terser)を無効化せずに画像プラグインを追加できます。これを忘れると JS が minify されなくなります。
  • quality: 80(JPEG / mozjpeg)は視覚劣化がほぼ分からない一方でサイズ削減効果が大きい実用値です。
  • imagemin の pngquant の quality は [min, max] のレンジ指定で、[0.65, 0.9] あたりが実用的です。
  • SVG は svgo の removeViewBox を無効化しておかないと、レスポンシブ表示でサイズが崩れることがあります。

3. WebP / AVIF を生成する(generator)

同じ元画像から次世代フォーマットを別出力するには generator を使います。sharp ベースの sharpGenerate を使うと JPEG/PNG から WebP / AVIF を生成できます(この API も 5.x で変わっていません)。

const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

new ImageMinimizerPlugin({
  generator: [
    {
      preset: "webp", // ?as=webp 付きimportをWebP化
      implementation: ImageMinimizerPlugin.sharpGenerate,
      options: {
        encodeOptions: { webp: { quality: 80 } },
      },
    },
  ],
});

利用側では query で変換を指定します。

import heroWebp from "./hero.jpg?as=webp";

HTML / テンプレート側では <picture> でフォールバックを組みます。

<picture>
  <source type="image/webp" srcset="/images/hero.webp" />
  <img src="/images/hero.jpg" alt="hero" width="1200" height="630" />
</picture>

確認方法

最適化が効いているかは、ビルド前後のファイルサイズ比較が最も確実です。

NODE_ENV=production npx webpack
du -sh dist/images
  • dist/images 配下の各画像を元ファイルと比較し、縮小されているか確認する
  • ブラウザの DevTools → Network でレスポンスサイズと、WebP が配信されているか(Content-Type: image/webp)を確認する
  • Lighthouse の「次世代フォーマットでの画像の配信」「効率的な画像エンコード」警告が消えているか確認する

注意点

  • ビルド時間が伸びる: sharp / imagemin は CPU を使うため、画像が多いとビルドが遅くなります。開発ビルドでは圧縮を無効化し、本番ビルド(CI)でのみ有効化するのが基本です。
  • pngquant は非可逆: PNG でも減色による劣化が起きます。ロゴやアイコンなど色数の少ない画像は品質レンジを上げるか対象から外す判断が要ります。
  • ネイティブ依存: sharp はネイティブバイナリに依存するため、CI のOS/アーキが開発環境と異なるとビルドで詰まることがあります。Docker でビルド環境を固定すると安定します。
  • 二重最適化を避ける: 元画像を手動で最適化済みの場合、さらに非可逆圧縮をかけると劣化が重なります。元画像は無加工に保ち、最適化はビルドに一本化するのが管理しやすい構成です。

まとめ

webpack の画像最適化は「Asset Modules で画像を扱う」「image-minimizer-webpack-plugin で本番時に圧縮」「必要なら WebP/AVIF を生成」の3点で組み込めます。設定は一度入れてしまえばビルドのたびに自動で効くため、転送量と LCP の改善をコストをかけずに継続できます。まずは sharp ベースで quality 80 あたりの実用値から始め、効果をファイルサイズと Lighthouse で測りながら調整するのがおすすめです。なお、本プラグインは deprecated のため、新規プロジェクトでは後継の minimizer-webpack-plugin への移行も併せて検討してください。

\ 最新情報をチェック /

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください。