投稿日:2021年8月1日
この記事では様々な環境でwebpack5で静的ファイルの公開設定をする方法について詳しく解説していきます。
この記事ではwebpack5で静的ファイルの公開設定をする方法について解説していきます。
まず軽くwebpackの歴史の話になります。
※興味がない方は飛ばしてください!
webpackの静的ファイルの扱いの設定方法はwebpack4までとwebpack5からで大きく異なります(後方互換あり)。
webpack4までは画像などの静的ファイルを管理するためにはurl-loader、raw-loaderやfile-loaderなどのLoadersがのインストールと読み込み設定が必要になっていました。
しかし、webpack5からはAsset Modules という機能が追加され、デフォルトで静的ファイルを管理できるようになりました。
この記事は以下の環境を前提にしています。
まずはもっとも基本となる、JavaScriptのコード上から静的ファイルを読み込む際の設定方法について解説します。
この節でのプロジェクトについて簡単に解説します。
プロジェクトの構成は下のようになっています。
.
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── images
│ │ └── hearts.svg
│ └── index.js
└── webpack.config.js
ここで./src/index.jsは画像ファイルの./src/images/hearts.svgを読み込む以下のようなコードになっています。
import mainImage from "./images/hearts.svg";
console.log(mainImage);
特になにも設定せずにwebpackのビルドをしてしまうと、上記コードimport文の"./images/hearts.svg"というパスがそのまま残ってしまいます。
このままでは./images/hearts.svgを手動でビルド後のjsファイルからの総体パスにコピーする必要があります。
静的ファイルはディレクトリを別名にしてまとめたり、CDNにデプロイしたりしたいのでこれはあまり好ましくありません。
Asset Modulesを使うとこれらの問題を簡単に解決できます。
早速Asset Modulesの機能を使うためのwebpackの設定をしましょう。
// [...]
const config = {
// [...]
module: {
rules: [
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
},
],
},
// [...]
};
Asset Modulesの機能は、ModuleのRule.typeをasset*
の値にすることで利用できます。
今回は静的ファイルを出力ディレクトリに別ファイルで出力したいのでtypeをasset/resourceにしました。
実際にビルドすると、以下のような出力を確認できます。
./dist
├── 64ea56b6f03140695a9e.svg
└── main.js
./src/images/hearts.svgがハッシュ値を名前に含んだファイル名である64ea56b6f03140695a9e.svgに変換され出力されました。ファイル名に含まれるハッシュ値は元ファイルのデータの内容から生成されるため、内容が変わるとビルド後のファイル名も変わります。
また、./src/index.jsから生成されたmain.js内の"./images/hearts.svg"は画像のパスの変更に合わせて"./64ea56b6f03140695a9e.svg"に変換されました。
先程の例では、静的ファイルは対象ディレクトリのルート直下に出力されていました。次に出力ディレクトリ、出力ファイル名を細かく設定してみましょう。出力パスはoutput.assetModuleFilenameで設定できます。
// [...]
const config = {
// [...]
output: {
// [...]
assetModuleFilename: 'assets/[hash][ext][query]'
},
// [...]
};
// [...]
ここではassetsというディレクトリに[hash][ext][query]というファイル名で出力するように設定しました。[hash]、[ext]、[query]は対象ファイルからそれぞれ特定の値が代入されます。
ビルドすると以下のように出力されます。
./dist
├── assets
│ └── 64ea56b6f03140695a9e.svg
└── main.js
無事、assetsというディレクトリに出力できました。
main.js内のパスも自動的に変換されています。
次にHTMLから静的ファイルを読み込む方法について解説します。
.
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── images
│ │ └── hearts.svg
│ ├── index.html
│ └── index.js
└── webpack.config.js
./src/index.htmlは下ののように./src/images/hearts.svgの相対パスを<img>タグのsrcとして指定しています。
<!DOCTYPE html>
<html>
<head>
<title>Sample Page</title>
</head>
<body>
<img src="./images/hearts.svg" />
</body>
</html>
先程の例と同じように、このパスもwebpackのAsset Modulesビルドでうまく変換できます!
ここでの設定は、追加のパッケージが必要になります。まずは必要なパッケージをインストールしましょう。
$ npm install --save-dev html-loader html-webpack-plugin
新しく登場したパッケージに対しての解説を簡単に載せておきます。
// [...]
const HtmlWebpackPlugin = require('html-webpack-plugin');
// [...]
const config = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'assets/[hash][ext][query]'
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html"
})
],
module: {
rules: [
{
test: /\.html$/i,
loader: "html-loader",
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
},
],
},
// [...]
};
// [...]
これでビルドすると以下のように出力されます。
./dist
├── assets
│ └── 64ea56b6f03140695a9e.svg
├── index.html
└── main.js
./dist/index.html内のパスもoutputの設定に合わせて変換されています。
最後に生のTypeScriptコードから静的ファイルを読み込む方法について解説します。
.
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── custom.d.ts
│ ├── images
│ │ ├── close.png
│ │ └── hearts.svg
│ ├── index.html
│ └── index.ts
├── tsconfig.json
└── webpack.config.js
./src/index.tsは以下のように./images/close.pngを読み込んでいます。
import CloseImg from "./images/close.png";
const imgElem = document.querySelector("#img-main") as HTMLImageElement;
imgElem.src = CloseImg;
このままだとTypeScriptのコンパイラは存在しないモジュール./images/close.pngのimportをエラーとして認識してしまいます。
TypeScriptのコンパイルエラーを防ぐためには、静的ファイルに対応する型宣言が必要になります。
※create-react-appなどで作成したアプリケーションにはこの型宣言がデフォルトで用意されています。
静的ファイルとしてよくある拡張子に対しての型宣言を作成してみましょう。
declare module '*.avif' {
const src: string;
export default src;
}
declare module '*.bmp' {
const src: string;
export default src;
}
declare module '*.gif' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.jpeg' {
const src: string;
export default src;
}
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.webp' {
const src: string;
export default src;
}
declare module '*.svg' {
const src: string;
export default src;
}
これでOKです!
ここからwebpackでTypeScriptを処理するための設定をしていきます。
まずはwebpackがTypeScriptのトランスパイルをするために必要なパッケージをインストールしましょう。
$ npm install --save-dev typescript ts-loader file-loader
const HtmlWebpackPlugin = require('html-webpack-plugin');
// [...]
const config = {
entry: './src/index.ts',
// [...]
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
};
// [...]
これでビルドするとJavaScriptと同じように静的ファイルを公開できます。