前書き
本記事のゴール
この記事では、Webpack開発環境において画像アセットを含むプロジェクトをビルドする方法を理解することを目指します。
この記事で解説する内容
- Webサイトにおける画像アセットの利用目的/方法一般論について説明します
- 画像アセットを含むプロジェクトをビルドする方法を説明します
- 簡単な実例を示します
この記事で解説しない内容
- 画像アセットの種類およびその性質については議論しません
- 画像の作成方法については説明しません
シリーズ記事
背景知識
画像アセットはWebサイトにおいてどのように利用されるか?
画像アセットは、Webサイトへの興味喚起およびコンテンツの理解促進といった目的で多くのWebサイトに採用されています。
Webサイトの主要な構成要素はHTML/CSS/JavaScriptですが、画像アセットはHTML/CSS/JavaScriptにおいてそれぞれ以下のように利用されます。
HTML
<img>
タグによって生成される画像要素において利用されます。
<img src="image.jpg">
[参考] HTML imgタグ
CSS
background-image
プロパティによって描画される背景画像として利用されます。
#element {
background-image: url(image.png);
}
[参考] CSS background-imageプロパティ
JavaScript
JavaScript(またはJavaScriptフレームワーク)における画像アセットの利用方法は大きく2つに分類することができます。
1つ目はJavaScriptプログラムによるDOM操作・スタイル操作の中で画像アセットを間接的に利用する方法です。DOM操作において <img>
タグを新規作成・編集する場合やスタイル操作において background-image
プロパティを追加・変更する場合等が考えられます。
// DOM操作
let parentElement = document.getElementById("element");
let imageChildElement = document.createElement("img");
imageChildElement.setAttribute("src", "image.jpg");
parentElement.appendChild(imageChildElement);
// スタイル操作
let backgroundImageElement = document.getElementById("background-image");
backgroundImageElement.style.backgroundImage = require("image.jpg");
[参考] JavaScript DOM操作
2つ目はJavaScriptフロントエンドフレームワーク(Vue.js等)のJavaScript系ファイル(*.vue
ファイル等)のHTMLテンプレート・CSSスタイルシートの中で画像アセットを直接的に利用する方法です。実質的には1つ目の方法と等価である場合が多いと思いますが、記述方法が大きく異なります。以下にVueファイルにおける実装例を示します。
<template>
<img v-bind:src="require('image.jpg')">
<div id="background-image"></div>
</template>
<style>
#background-image {
background-image: url("image.png");
}
</style>
画像アセットはWebpackビルドにおいてどのように処理されるか?
画像アセットは、基本的にはHTML/CSS/JavaScriptファイルの依存アセットとしてWebpackのビルドプロセスに取り込まれます。
Webpackはプロジェクトに含まれる多数のアセットを1つ以上のモジュールにまとめて指定ディレクトリに出力するという仕事を担っています。最も基本的な仕事は「エントリポイントに指定されたファイル群が依存するファイル群をモジュールにまとめる」ことで、主にエントリポイントとなるJavaScriptファイルとそれが依存するJavaScript系ファイル群を1つのJavaScriptモジュールに統合するために利用されています(適切な設定を加えることで、JavaScript系ファイルだけでなくHTML/CSS等のファイルも利用可能です)。JPEG/PNG等の画像アセットは、HTML/CSS/JavaScriptファイルの依存アセットとして検出されることでWebpackのビルド対象となります1。
さらに、Webpackではビルド対象のファイル群に対して変換処理等を実行することができます。特にJPEG/PNG等の画像アセットに対して、可逆/非可逆圧縮および様々な変換処理を施した上でその結果を出力させることができます。画像アセットの最適化はWebサイトのパフォーマンスを改善するために必要な項目の1つですが、その問題を部分的に解決することができます。
要約すると、画像アセットはWebpackのビルドプロセスにおいて HTML/CSS/JavaScript の依存アセットとして認識され、HTML/CSS/JavaScript とともに指定ディレクトリに出力されます。このとき、適切な設定を記述することで、画像アセットの圧縮や変換を施すこともできます。
方法
サンプルコードをGitHubで公開しています。記事と併せてご覧ください。
/website/website-dev-on-webpack5/image-asset-on-webpack5
実装例の説明では、ファイル/フォルダパスを ルート相対パス の形式で記述しています。ここでルートはプロジェクトルートまたはWebサイトのドキュメントルートに相当します。
前提
WebpackおよびSCSS等のnpmパッケージの利用には Node.js が必要です。
インストールが未実施の場合には、以下のリンクからインストールしておいてください。
また、動作確認を行うためのNode.jsプロジェクトを作成しておきます。
適当な名前のディレクトリ(ここでは my-image-project
)を用意した上で、そのディレクトリにおいて npm init
コマンドを実行することでNode.jsプロジェクトを新規作成することができます。
# my-image-projectディレクトリの作成
mkdir /path/to/my-image-project
cd /path/to/my-image-project
# Node.jsプロジェクトの作成
npm init
npm init
コマンドを実行するとプロジェクトに関する情報を入力するよう促されますが、今回は適当な設定にしておいて問題ありません。今後必要に応じて適宜変更しましょう。
コマンド実行完了後、 /package.json
という設定ファイルが新規作成されていることを確認したら事前準備完了です。
手順概要
-
npmパッケージを導入する
Webpackの利用に必要なnpmパッケージwebpack
webpack-cli
および動作確認用のHTML/CSSをビルドするのに必要なnpmパッケージhtml-webpack-plugin
html-loader
css-loader
mini-css-extract-plugin
をプロジェクトに追加します。 -
Webpackの設定ファイルを記述する
JPEG/PNG画像アセットの処理手順やルールを設定ファイル/webpack.config.js
に記述します。 -
JPEG/PNGファイルを準備する
JPEG/PNG画像アセット/src/assets/images/favicon-[html,css,js].[jpg,png]
を準備します。 -
HTML/CSS/JavaScriptファイルを作成する
動作確認用のHTMLファイル/src/source.html
・CSSファイル/src/css/source.css
・JavaScriptファイル/src/js/source.js
を作成します。 -
Webpackビルドを実行する
Webpackビルドを実行することで、HTMLファイル/dist/target.html
・CSSファイル/dist/css/target.css
・JavaScriptファイル/dist/js/target.js
および画像アセットファイル/dist/images/favicon-[html,css,js].[jpg,png]
を出力します。
手順詳細
[手順1] npmパッケージを導入する
今回はWebpackを利用して画像アセットを含むプロジェクトのビルド作業を行います。
まず初めに、Webpackを利用するのに必要な webpack
webpack-cli
パッケージをインストールします。
Webpack5(version5.X.X)をインストールするため、バージョン指定を行っています。
npm install --save-dev webpack@5 webpack-cli@5
次に、動作確認用HTML/CSS/JavaScriptファイルをバンドルするのに必要な html-webpack-plugin
html-loader
css-loader
mini-css-extract-plugin
パッケージをインストールします。
npm install --save-dev html-webpack-plugin html-loader css-loader mini-css-extract-plugin
[手順2] Webpackの設定ファイルを記述する
今回は以下の2つの処理を実行するためのWebpackの設定を記述します。
-
JPEG/PNG画像アセットを最適化して指定ディレクトリに出力する
- JPEG/PNG画像
/src/assets/images/favicon.jpg
/src/assets/images/favicon.png
をコピー&ペーストして/dist/images/favicon.jpg
/dist/images/favicon.png
に出力します。
- JPEG/PNG画像
-
動作確認用のHTML/CSS/JavaScriptを出力する
- HTMLファイル
/src/source.html
から/dist/target.html
を生成します。 - CSSファイル
/src/css/source.css
から/dist/css/target.css
を生成します。 - JavaScriptファイル
/src/js/source.js
から/dist/js/target.js
を生成します。 - HTML/CSS/JavaScriptファイル生成の際、画像アセットのパスを自動的に調整するようにします。
- HTMLファイル
実装例において、ソース画像アセットの格納場所を /src/assets/images/
、配信用画像アセットの出力場所を /dist/images/
にしている点に注意してください。このようなディレクトリ構成にする理由は、「ソースHTML/CSS/JavaScriptファイルから見たソース画像アセットの相対パス」と「配信用HTML/CSS/JavaScriptファイルから見た配信用画像アセットの相対パス」が異なるような状況を作り出すためです。
こうした状況においても、Webpack(および関連プラグイン)はアセットの依存関係や相対パスを適切に解決することができるということを実装例で確認します。
これを実現する必要最小限のWebpack設定を以下に示します。
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = function() {
return {
mode: "development",
entry: {
/**
* /src/css/source.css -> OUTPUT_DIRECTORY/css/target.css
* /src/js/source.js -> OUTPUT_DIRECTORY/js/target.js
*/
"css/target": path.resolve(__dirname, "src/css/source.css"),
"js/target": path.resolve(__dirname, "src/js/source.js"),
},
output: {
/**
* [path]
* OUTPUT_DIRECTORY = /dist
*/
path: path.resolve(__dirname, "dist"),
/**
* [filename]
* XXX/YYY.ZZZ -> OUTPUT_DIRECTORY/YYY.js
*/
filename: "[name].js",
/**
* [assetModuleFilename]
* XXX/YYY.ZZZ -> OUTPUT_DIRECTORY/images/YYY.ZZZ
*/
assetModuleFilename: "images/[name][ext]",
},
module: {
rules: [
{
test: /\.(jpg|png)$/,
type: "asset/resource",
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: {
sources: {
list: [
{
tag: "img",
attribute: "src",
type: "src",
}
],
},
},
}
]
},
{
test: /\.css$/,
use: [
/**
* CssLoader -> MiniCssExtractPluginLoader
*/
MiniCssExtractPlugin.loader,
"css-loader",
],
},
]
},
plugins: [
new HtmlWebpackPlugin({
/**
* /src/source.html -> OUTPUT_DIRECTORY/target.html
*/
template: path.resolve(__dirname, "src/source.html"),
filename: "target.html",
inject: false,
}),
new MiniCssExtractPlugin(),
],
}
}
JPEG/PNG画像アセット出力に関する設定で特に注目すべきポイントは以下の2点です。
- モジュール設定(
module.rules
) - 出力設定(
output
)
Webpackの module.rules
設定では、Webpackがビルド対象とするファイルからモジュールを生成するための変換処理等を記述します。ここではJPEG/PNG画像ファイルに対して Asset Modules の設定、HTMLファイルに対して html-loader
、CSSファイルに対して css-loader
を適用するよう定めています。
Asset Modules とは、各種アセットファイルを利用するためのWebpackの仕組みです。Webpack4までは画像・フォント等のアセットを利用するために file-loader
raw-loader
url-loader
といったローダを導入する必要がありましたが、Webpack5からはnpmパッケージを追加することなく module.rules.type
設定を記述するだけで済むようになりました。
[参考] raw-loader / url-loader / file-loader
Asset Modulesでは、module.rules.type
設定として以下の4種類が用意されています。
-
{ type: "asset/resource" }
: アセットをファイルとして出力します -
{ type: "asset/source" }
: アセットを文字列として出力します -
{ type: "asset/inline" }
: アセットをインライン化してバンドルに組み込みます -
{ type: "asset" }
: 上記設定を自動的に決定します
[参考] Asset Modules
今回は「JPEG/PNG画像アセットファイルを指定ディレクトリ /dist/images
に出力する」ことを目指しているので、 { type: "asset/resource" }
を指定しています。
一方、HTML/CSSファイルに対してはそれぞれ html-loader
/ css-loader
を適用するよう定めています。
html-loader
css-loader
は、HTML/CSSファイルを(JavaScriptファイルをベースとする)Webpackのビルドプロセスで取り扱うことができるようにする非常に重要なローダーです。html-loader
css-loader
の重要な役割の1つにHTML/CSSファイル中に含まれる依存関係の検出があり、ここではその機能を利用して画像アセットの取り込みを実現しています。
html-loader
css-loader
は、デフォルト設定の状態ではHTML/CSSのパース+依存関係の検出を行うようになっています。今回の実装例では、HTMLファイルにおけるCSS/JavaScript読み込みの依存関係は手動で解決することにしているので、 html-loader
に対して「画像アセット読み込み(<img src="XXX"
)の依存関係のみ解決してほしい」という命令をしています。html-loader
css-loader
のオプションの詳細に関しては公式のGitHubレポジトリをご覧ください。
[参考] html-loader / css-loader
output
設定では、ビルド出力結果のファイル名を指定します。 output
設定項目は数多くあるため、ここでは最も頻繁に使用する3つについて簡単に紹介します。
-
output.path
:出力ディレクトリを指定する -
output.filename
:出力ファイル名を指定する -
output.assetModuleFilename
:(画像等のアセットの)出力ファイル名を指定する
Webpack公式サイトのoutput設定 に記述されているように、 output.assetModuleFilename
はAsset Modulesの出力ファイル名を指定するものです。画像アセットを特別なディレクトリで管理する場合に便利だと思います2。
Webpackの出力設定は最も基本的な設定の1つですが、この記事では詳細を割愛します。詳細な情報に関しては、Webpack公式ドキュメントをご参照ください。
[参考] Webpackの出力設定
[手順3] JPEG/PNGファイルを準備する
手順3では、Webサイトに掲載する画像アセットを準備します。
画像変換ソフト等を利用して、2種類の形式(JPEG/PNG)の画像を作成しておきます。
ここでは、HTML/CSS/JavaScriptファイルにおける画像アセット読み込みの動作確認を行うため、オリジナル画像を複製して6枚の画像アセットファイルを作成することにしましょう。6枚の画像の配置場所は以下の通りとします(HTML/CSS/JavaScriptの実装例は手順4で示します)。
- HTML用JPEG画像:
/src/assets/images/favicon-html.jpg
- HTML用PNG画像:
/src/assets/images/favicon-html.png
- CSS用JPEG画像:
/src/assets/images/favicon-css.jpg
- CSS用PNG画像:
/src/assets/images/favicon-css.png
- JavaScript用JPEG画像:
/src/assets/images/favicon-js.jpg
- JavaScript用PNG画像:
/src/assets/images/favicon-js.png
[手順4] HTML/CSS/JavaScriptファイルを作成する
手順4では、HTML/CSS/JavaScriptから画像アセットを適切に読み込むができるということを確認するために、動作確認用のHTML/CSS/JavaScriptファイルを作成します。
上記設定ファイル /webpack.config.js
では入力HTML/CSS/JavaScriptファイルパスをそれぞれ /src/source.html
/src/css/source.css
/src/js/source.js
に指定したので、その位置に source.html
source.css
source.js
ファイルを作成しましょう。
ファイル内容はどんなものでも構いませんが、ここでは例として以下のように記述することにします(折りたたみ要素の中にあります)。
/src/source.html の入力内容例
<!doctype html>
<html>
<head>
<title>My Image Project</title>
<link rel="stylesheet" href="./css/target.css">
</head>
<body>
<h1>My Image Project</h1>
<p><img>要素・PNG画像</p><img src="./assets/images/favicon-html.png" width="100" height="100">
<p><img>要素・JPEG画像</p><img src="./assets/images/favicon-html.jpg" width="100" height="100">
<p>background-image属性・PNG画像</p><div id="png-image" class="image"></div>
<p>background-image属性・JPEG画像</p><div id="jpeg-image" class="image"></div>
<script src="./js/target.js"></script>
</body>
</html>
/src/css/source.css の入力内容例
.image {
width: 100px;
height: 100px;
}
#jpeg-image {
background-image: url(../assets/images/favicon-css.jpg);
background-size: cover;
}
#png-image {
background-image: url(../assets/images/favicon-css.png);
background-size: cover;
}
/src/js/source.js の入力内容例
function createTextElement(text) {
const textElement = document.createElement("p");
textElement.textContent = text;
return textElement;
}
function createPngImgElement() {
const pngImage = require("../assets/images/favicon-js.png");
const pngImgElement = document.createElement("img");
pngImgElement.setAttribute("src", pngImage);
pngImgElement.width = 100;
pngImgElement.height = 100;
return pngImgElement;
}
function createJpegImgElement() {
const jpegImage = require("../assets/images/favicon-js.jpg");
const jpegImgElement = document.createElement("img");
jpegImgElement.setAttribute("src", jpegImage);
jpegImgElement.width = 100;
jpegImgElement.height = 100;
return jpegImgElement;
}
document.addEventListener("DOMContentLoaded", function() {
const bodyElement = document.body;
bodyElement.appendChild(createTextElement("JavaScript由来<img>要素・PNG画像"));
bodyElement.appendChild(createPngImgElement());
bodyElement.appendChild(createTextElement("JavaScript由来<img>要素・JPEG画像"));
bodyElement.appendChild(createJpegImgElement());
});
[手順5] Webpackビルドを実行する
手順1〜4で画像アセット出力およびその動作確認のための準備が完了しました!
それではWebpackを用いて /src/assets/images/favicon-[html,css,js].[jpg,png]
から /dist/images/favicon-[html,css,js].[jpg,png]
を生成してみましょう。
ターミナルで npx webpack
コマンドを実行することでビルド作業が開始します。
# ディレクトリ移動
cd /path/to/my-image-project
# Webpackコマンド実行
npx webpack
ビルドが正常に完了すれば、画像アセットファイル /dist/images/favicon-[html,css,js].[jpg,png]
が生成されていることを確認できると思います。/dist/target.html
をWebブラウザ等で確認すると、画像アセットの読み込みが正常に動作している様子を見ることができるはずです。
また、/dist/target.html
/dist/css/target.css
/dist/js/target.js
の内容を見ると、画像アセット読み込みのパスが正しく解決されていることを確認できると思います。例えば、/dist/target.html
では <img>
タグが以下のように変更されていることを確認することができます。
<img src="./assets/images/favicon-html.png" width="100" height="100">
<img src="images/favicon-html.png" width="100" height="100">
参考までに、/dist/target.html
/dist/css/target.css
の出力結果全体を載せておきます(/dist/js/target.js
の出力結果は非常に長くなるため省略します)。
/dist/target.html の出力内容例
<!doctype html>
<html>
<head>
<title>My Image Project</title>
<link rel="stylesheet" href="./css/target.css">
</head>
<body>
<h1>My Image Project</h1>
<p><img>要素・PNG画像</p><img src="images/favicon-html.png" width="100" height="100">
<p><img>要素・JPEG画像</p><img src="images/favicon-html.jpg" width="100" height="100">
<p>background-image属性・PNG画像</p><div id="png-image" class="image"></div>
<p>background-image属性・JPEG画像</p><div id="jpeg-image" class="image"></div>
<script src="./js/target.js"></script>
</body>
</html>
/dist/css/target.css の出力内容例
/*!**********************************************************************!*\
!*** css ./node_modules/css-loader/dist/cjs.js!./src/css/source.css ***!
\**********************************************************************/
.image {
width: 100px;
height: 100px;
}
#jpeg-image {
background-image: url(../images/favicon-css.jpg);
background-size: cover;
}
#png-image {
background-image: url(../images/favicon-css.png);
background-size: cover;
}
後書き
サンプルコード
/website/website-dev-on-webpack5/image-asset-on-webpack5
-
技術的には、画像アセットをエントリポイントとしてビルド対象に含めたり、Webpackローダー/プラグインを利用して特殊な形でビルドの出力結果に含めたりすることもできます。実際、プロジェクトの事情によってはそのようなアプローチを採用する方が良い場合もあるかもしれません。ただ、「Webサイトにおいて画像アセットはHTML/CSS/JavaScriptファイルが利用する構成要素である」という観点を踏まえると、画像アセットを依存アセット/モジュールとして扱う方が管理しやすいように思います。 ↩
-
module.rules.type
プロパティが"asset/resource"
等に設定されているとき、module.rules.generator.filename
プロパティでアセットファイルの出力パスを設定することもできます。詳細は Webpack公式サイトのAsset Modulesの説明 をご覧ください。 ↩