Webpack5でCSSとJavaScriptで読み込んだ画像のパスが一致しない場合
まえがき
webpack のかなり古いバージョンから(v2系)一気に最新(v5系)にアップデートしたので、そのときのメモと、
CSS ファイル内で、
background-image:url("./img/me.jpg");
としたときと、 JavaScript ファイル内で、
import image from 'static_img/me.jpg'
と読み込んだ場合に、画像のパスが同じにならず CSS ファイル内の画像が読み込まれない現象が発生したので、それのメモになります。
これは v2系
のときには発生せず v5系
に上げた際に発生したので、大掛かりなアップデートをする方が対象になるかと思います。
hisasann/webpack-v5-with-file-loader
前提条件
npmモジュールのバージョンたち
このようなバージョンで試しています。
react は意図的に v16 を使っていますが、ここは v17 にしても問題ありません。
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"babel-loader": "^8.2.3",
"babel-plugin-module-resolver": "^4.1.0",
"css-loader": "^6.5.1",
"file-loader": "^6.2.0",
"image-webpack-loader": "^8.0.1",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-refresh": "^0.11.0",
"style-loader": "^3.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.64.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
webpack v2以下のwebpack.config.jsの書き方
webpack v2系 のCSS・JavaScriptの画像パスを解決してくれるサンプル
let config = {
mode: environment,
entry: {
main: ['./src/index.jsx'],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public'),
},
resolve: {
extensions: ['.jsx', '.js'],
},
module: {
rules: [
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '/images/[name].[hash:7].[ext]',
limit: 10000,
},
},
],
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
url: true,
},
},
],
},
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [environment === 'development' && require.resolve('react-refresh/babel')].filter(Boolean),
},
},
},
],
},
plugins: [
],
}
webpack v5以下のwebpack.config.jsの書き方
webpack v5系 のCSS・JavaScriptの画像パスを解決してくれるサンプル
const webpack = require('webpack')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
const path = require('path')
const environment = process.env.NODE_ENV || 'development'
process.noDeprecation = true
// 共通の設定
let config = {
mode: environment,
entry: {
main: ['./src/index.jsx'],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public'),
},
resolve: {
extensions: ['.jsx', '.js'],
},
module: {
rules: [
{
test: /\.(png|jpg|gif)$/i,
generator: {
filename: 'images/[name][ext][query]'
},
type: 'asset/resource'
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
url: true,
},
},
],
},
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [environment === 'development' && require.resolve('react-refresh/babel')].filter(Boolean),
},
},
},
],
},
plugins: [
],
}
// development環境設定
if (environment === 'development') {
config = Object.assign(config, {
plugins: [
...config.plugins,
new webpack.HotModuleReplacementPlugin(),
new ReactRefreshWebpackPlugin(),
],
devServer: {
// Dev server client for web socket transport, hot and live reload logic
hot: true,
historyApiFallback: true,
static: {
directory: path.join(__dirname, 'public'),
},
},
})
}
module.exports = config
重要なポイント
webpack v2
v2 では file-loader
を使って画像ファイルを特定のディレクトリ images
にコピーしていました。
そして、そのパス含めたファイル名が CSS や JavaScript でインポートしたところで書き換えられていました。
{
test: /\.(jpe?g|png|gif|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '/images/[name].[hash:7].[ext]',
limit: 10000,
},
},
],
},
webpack v5
v5 では v2 の書き方だと、なぜか CSS 側には file-loader
の部分がうまく効かず /images/
というパスがつかない URL が指定されてしまって 404 を返してしまっていました。
(ここには大いにハマりました。)
ので、以下のように v5 での書き方に変更しました。
css-loader
に options で url: true
で明示的に CSS 内の url()メソッド
を取り込んでいます。
ここは default value が true なので、あまり気にしなくてよいですが、 false だと url を require に置き換えてくれなくなるので、画像ファイルのロードができなくなります。
そして type: 'asset/resource'
ここを追加することで v2 で file-loader
を使って画像ファイルのパスを変更した仕組みが v5 でもちゃんと動くようになります。
シンプルになりましたね。
{
test: /\.(png|jpg|gif)$/i,
generator: {
filename: 'images/[name][ext][query]'
},
type: 'asset/resource'
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
url: true,
},
},
],
},
番外編:Fast Refresh
webpack を使っていて、さらに React で JSX を書いていると、なにか変更したら live reload ではなく HMR が走るのがありがたい時代がありました。
Hot Module Replacement | webpack
コードを変更したら自動的にロードしてくれる仕組みを少しまとめると、
- live reload - 画面全体をリロードする
- HMR - 変更があったコンポーネントを差し替えてくれる
- ただし、エントリーポイントや Redux 側にコードを足す必要がある
- Fast Refresh
- 実際のコードには何も足さなくてよい、最高!
ということで、 Fast Refresh
を試してみました。
webpack の plugin
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production';
module.exports = {
mode: isDevelopment ? 'development' : 'production',
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [isDevelopment && require.resolve('react-refresh/babel')].filter(Boolean),
},
},
],
},
],
},
plugins: [isDevelopment && new ReactRefreshWebpackPlugin()].filter(Boolean),
};
こんな感じで実現されるのでかなり便利です。
今回用に用意したリポジトリに実は導入しています。
hisasann/webpack-v5-with-file-loader
仕組みはこちらの zenn のスクラップに書いてありました。
あとがき
webpack はどうやら v4 で大きく変わり、その後の v5 でも大きく変わったようで、 v2 -> v5 はなかなかググるのが大変でした。
とにかく小さくプロジェクトを作ってみて、いろいろ試してみて、ダメだったら戻すという作業を繰り返して検証しました。
そんなほんの一角の CSS と JavaScript 内の画像ファイルのパスの不一致についてメモしました。
いやはや、ほんとこれは氷山の一角なんですよ。
では、ぼくはまだある webpack の検証に戻りますね。
良い週末を!
参考資料
What is the loader order for webpack? - Stack Overflow
ちゃんと理解するWebpack5。2:Babel、画像の処理と複数バンドル
最新版で学ぶwebpack 5入門 - スタイルシート(CSS/Sass)を取り込む方法 - ICS MEDIA
webpack1からwebpack5に上げた - Qiita
【webpack】file-loaderで画像などの出力先を条件分岐させる方法 - Qiita
【React】Fast Refresh を有効にする - Qiita
Discussion