お役立ちコラム
フロントエンド

Webpackの「Asset Modules」機能を使って画像を処理する方法

※本記事には広告が含まれます

Webpackは、エントリポイントを起点に依存関係をたどり、複数のJavaScriptファイルを1つにバンドルします。

その過程で、JSやCSSから画像ファイルへの参照を検知すると、それらもプロジェクトのアセットとしてバンドル対象に含めます。

今回は、Webpackの「Asset Modules」機能を用いて、これらの画像を処理する方法を解説します。

※本記事は、Webpack 5(v5.104.1)を使用した環境を前提としています。

Webpackで画像をバンドルする方法

エントリーポイントでインポートされているSassファイル内で背景画像等の指定がある場合、その画像ファイルはWebpackによって依存関係として認識され、バンドルの対象になります。

.pageheader {
  background-image: url("./img/pageheader_bg.jpg");
  background-size: cover;
}

h1 {
  font-size: 30px;
  display: flex;

  &:hover {
    color: red;
  }
}
import { sayHello } from "./sub";
import "./style.scss";

sayHello("Hi, Webpack");

そのため、画像ファイルに対してもWebpack側で適切な設定を行う必要があります。

Webpack 4では、画像を読み込むために新たにローダーを追加する必要がありましたが、Webpack 5以降では、画像やフォントなどの静的ファイルを扱うために「Asset Modules」という仕組みが用意されています。

Asset Modules は、他のローダーと同様にmodule.rules配列の中に記述して設定します。

const path = require("node:path"); // Node.jsに標準で用意されているpathモジュールの読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.js", // エントリーポイントの指定
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js", // 出力時のファイル名の指定
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: (pathData) => {
        // エントリー名が'main'の場合は'style.css'を返す
        // それ以外はエントリー名そのまま([name].css)にする
        return pathData.chunk.name === "main"
          ? "style.[contenthash].css"
          : "[name].[contenthash].css";
      },
    }),
    new HtmlWebpackPlugin({
      // ビルド時に使用するHTMLテンプレートを指定
      template: "./src/index.html",
      // 生成されたスクリプトタグを挿入する位置(bodyの末尾)
      inject: "body",
      // このHTMLに自動で読み込ませるエントリーポイントを指定(複数指定する場合は配列の中でカンマ区切りで指定)
      chunks: ["main"],
    }),
  ],
  module: {
    rules: [
      {
        test: /\.scss$/, //どの拡張子のファイルに適用させるかを指定
        use: [
          //変換されたCSSをJavaScriptとは別のCSSファイルとして抽出
          MiniCssExtractPlugin.loader,
          //CSSをJavaScriptからimportできる形に変換
          "css-loader",
          //WebpackからPostCSSを呼び出して実行
          "postcss-loader",
          //Webpackからsassを呼び出してSass→CSSを実行
          "sass-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        //画像を別ファイルとして出力
        type: "asset/resource",
      },
    ],
  },
};

上の例では、typeを「asset/resource」と設定していますが、この設定を行うことで、画像ファイルはJavaScriptにバンドルされるのではなく、ビルドディレクトリ直下に複製・出力されます。(Webpack 4まで使用されていたfile-loaderと同等の挙動。)

type値意味
asset/resource画像を別ファイルとして出力し、そのURLをJavaScriptやCSS側に渡す。
asset/inline画像をBase64(data URI)としてJavaScriptやCSSに埋め込む。
assetファイルサイズに応じて、Base64(data URI)として埋め込むか、別ファイルとして出力するかを自動的に切り替える。

また、デフォルトではファイル名がハッシュ値を使った名前(以下では6bc7b07e373c60e65f7e.jpg)で出力されます。

ハッシュとは?

ハッシュとは、特定の計算手順を用いて、元のデータを一見ランダムに見える値に変換することを言います。

このとき、変換に用いる計算手順を「ハッシュ関数」、それによって算出された値を「ハッシュ値」と呼びます。

(関連記事)Webpackで出力ファイルにハッシュ値を自動で付ける方法!

これをビルドディレクトリ直下ではなく、dist/imgフォルダにまとめたい場合は、新たにgenerator.filenameを指定します。

const path = require("node:path"); // Node.jsに標準で用意されているpathモジュールの読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.js", // エントリーポイントの指定
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js", // 出力時のファイル名の指定
  },
  plugins: [
    //省略
  ],
  module: {
    rules: [
      {
        test: /\.scss$/, //どの拡張子のファイルに適用させるかを指定
        use: [
          //変換されたCSSをJavaScriptとは別のCSSファイルとして抽出
          MiniCssExtractPlugin.loader,
          //CSSをJavaScriptからimportできる形に変換
          "css-loader",
          //WebpackからPostCSSを呼び出して実行
          "postcss-loader",
          //Webpackからsassを呼び出してSass→CSSを実行
          "sass-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        //画像を別ファイルとして出力
        type: "asset/resource",
        generator: {
          //出力先とファイル名を指定
          filename: "img/[name].[contenthash][ext]",
        },
      },
    ],
  },
};

※[ext]には、先頭にドット(.)が含まれているため、[name].[contenthash].[ext]にしてしまわないように注意。

これで、画像ファイルは「元のファイル名.ハッシュ値.拡張子」の名前でdist配下のimgフォルダにまとめて出力されます。

HTMLの<img>タグに記述したパスもWebpackに検知させる方法

HTMLの<img>タグに記述したパスもWebpackに検知させて、Asset Modulesの対象にしたい場合は、html-loaderをインストールします。

npm install --save-dev html-loader

インストールができたら、module.rules配列の新たに設定を追加します。

const path = require("node:path"); // Node.jsに標準で用意されているpathモジュールの読み込み
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "production",
  entry: "./src/index.js", // エントリーポイントの指定
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].[contenthash].js", // 出力時のファイル名の指定
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: (pathData) => {
        // エントリー名が'main'の場合は'style.css'を返す
        // それ以外はエントリー名そのまま([name].css)にする
        return pathData.chunk.name === "main"
          ? "style.[contenthash].css"
          : "[name].[contenthash].css";
      },
    }),
    new HtmlWebpackPlugin({
      // ビルド時に使用するHTMLテンプレートを指定
      template: "./src/index.html",
      // 生成されたスクリプトタグを挿入する位置(bodyの末尾)
      inject: "body",
      // このHTMLに自動で読み込ませるエントリーポイントを指定(複数指定する場合は配列の中でカンマ区切りで指定)
      chunks: ["main"],
    }),
  ],
  module: {
    rules: [
      {
        test: /\.scss$/, //どの拡張子のファイルに適用させるかを指定
        use: [
          //変換されたCSSをJavaScriptとは別のCSSファイルとして抽出
          MiniCssExtractPlugin.loader,
          //CSSをJavaScriptからimportできる形に変換
          "css-loader",
          //WebpackからPostCSSを呼び出して実行
          "postcss-loader",
          //Webpackからsassを呼び出してSass→CSSを実行
          "sass-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        //画像を別ファイルとして出力
        type: "asset/resource",
        generator: {
          //出力先とファイル名を指定
          filename: "img/[name].[contenthash][ext]",
        },
      },
      {
        test: /\.html$/,
        //HTMLをJavaScriptからimportできる形に変換
        use: ["html-loader"],
      },
    ],
  },
};

これで、HTMLファイルをJavaScriptファイルからimportできるようになり、エントリーポイントからimportされているHTMLファイルについては、html-loaderを通してWebpackが内容を解析・処理できるようになります。

ただし、HtmlWebpackPluginを使用している場合は、JavaScriptから明示的にimportしていなくても、プラグイン側でtemplateに指定したHTMLファイルについては、html-loaderを通してWebpackが処理することになります。

これでnpx webpackコマンドを実行すると、HTML内で使用している画像についてもAsset Modulesの設定が適用され、dist/imgディレクトリ内に出力されます。

また、出力先のHTML側では、画像の参照パスが自動的に書き換えられます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Webpackの使い方</title>
</head>

<body>
  <div class="pageheader">
    <h1>ここにテキストが入ります。</h1>
  </div>
  <img src="./img/logo.svg" alt="ロゴ">
</body>

</html>

まとめ

今回は、Webpackの「Asset Modules」機能を使って画像を処理する方法を解説しました。

次回は、開発用と本番用でWebpackの設定ファイル(webpack.config.js)を分割する方法を解説します。

今回は以上になります。最後までご覧頂き、ありがとうございました。

Webサイトのスポット修正・保守ならお任せ下さい!

ちょっとした修正や調整をしたいけれど、「制作会社に頼んだら高額になりそう」「どこに相談すればいいかわからない」そんなお悩みはありませんか?

弊所「E-VALUE WORKS」では、テキスト修正や軽微な調整などのスポット対応から、月額での保守・管理(外部Web担当)まで、ニーズに合わせたWebサイトの修正・保守サービスを提供しています。

「これって依頼できる?」という段階でも構いません。まずはお気軽にご相談ください。

E-VALUE WORKS アイコン
HOMEお役立ちコラムフロントエンドWebpackの「Asset Modules」機能を使って画像を処理する方法