webpack4からwebpack5に移行しようとして格闘したお話

先日、[webpackについて学んでみた](https://qiita.com/AKITO-927/items/ce705a5e7d4c9db7286a)という記事を書いたところ
私の中では過去1番の反響を頂きました、ありがとうございます :bow_tone1:

今回は、前回の記事を書いた後にwebpackと格闘した（悩まされた）お話を書こうと思います。
前提として、今回webpack4で書かれた設定をwebpack5に移行した際に発生した問題となります。

# 1. CSSに書かれている`url()`をwebpackが解決できない

webpackには`resolve.alias`という素敵な機能があります。（[公式ページ](https://webpack.js.org/configuration/resolve/#resolvealias)）
簡単に説明すると、今回問題となったようなCSSの中の`URL`や、JSのimportのパスなどを
webpackでビルドする際にいい感じに解釈してくれる機能です。

例（公式からコピペ）
```javascript:webpack.config.js
const path = require('path');

module.exports = {
  //...
  resolve: {
    alias: {
      Utilities: path.resolve(__dirname, 'src/utilities/'),
      Templates: path.resolve(__dirname, 'src/templates/'),
    },
  },
};
```
こういう設定を書いておくと
```javascript
import Utility from '../../utilities/utility';
```
という記述が
```javascript
import Utility from 'Utilities/utility';
```
という風にスッキリ書けます、素敵ですね！

では、今回何が問題だったのかというと
```scss:_vars.scss
$img-path: '/assets/**/images';
```
という宣言をして
```scss:_style.scss
background-image: url(#{g.$img-path}/loading.svg);
```
と書いたてビルドしたところ
```shell
Module not found: Error: Can't resolve '/assets/**/images/loading.svg' in '/
DocRoot/src/**/public/assets/**/css'
```
と怒られました。

なお、ファイルの配置は以下のようになっています（先に書けよ）
```
/DocRoot
L **
  + /public
  | L /assets
  |   L /**
  |     L /css
  |       + /globals
  |       |  L _var.scss
  |       + /pages
  |       |  L _style.scss
  |       + /images
  |          L loading.svg
  +- webpack.config.js
```

あれ〜？　`resolve.alias`が効いてない？
```javascript:webpack.config.js
resolve: {
  alias: {
   'assets': path.resolve(__dirname, './public/assets'),
  }
}
```

試行錯誤した結果、結論、以下で解決しました。
```javascript:webpack.config.js
resolve: {
  alias: {
   '/assets': path.resolve(__dirname, './public/assets'),
  }
}
```

どうやら4までは名前？で解決？していたようですが、5からはパスで解決するようになったみたいです。
（すみません、これに関しては試行錯誤の結果で裏付けがないです）

でもまさか、先頭に`/`つけて解決するとは思ってませんでした…

# 2. `file-loader`がないと怒られる
先の問題が解決して、無事にビルドも通ったし、さぁ`webpack-dev-server`を立ち上げるぞ〜！
と意気込んだ矢先

```shell
Module not found: Error: Can't resolve 'file-loader' in '/Docroot/src/**'
```
え？何でやねん。
で、`package.json`を確認したところ`file-loader`がなかったのでインストール

公式ドキュメント: [file-loader](https://v4.webpack.js.org/loaders/file-loader/)

無事にエラーなく`webpack-dev-server`が立ち上がってめでたしめでたし:clap_tone1:
…とはなりませんでした。（エラーは解消したのですが）

# 3. ファイル名がハッシュ化される
無事に`webpack-dev-server`を立ち上げられたのですがページを開いたところコンソールにエラーが…

```shell
/assets/**e3a410f961d2d303a588.svg/ 404 Not found
```
途中のスラッシュ抜けてるし`images`消えてるし誰だよお前な名前に変わっているしで混乱。
ちなみに`webpack.config.js`には以下のような記述をしていました。
```javascript:webpack.config.js
module: {
  rules: [
    {
      test: /\.(png|jpg|gif|svg)$/,
      loader: 'file-loader',
      options: {
        name: '[name].[ext]',
        outputPath : 'images/',
        publicPath : function(path){
          return '/assets/**/images/' + path
        }
      }
    }
  ]
}
```
どうやらこの設定が効いていない様子。
試行錯誤した結果、

### 悲報、`file-loader`、いらない子であることが判明:sob:

実は`file-loader`が必要だったのは`4`までで、`5`からは非推奨でした…
この記事を書いていて気づいたのですが、`file-loader`の公式ページも`4`のものでした…

じゃあどうするのか、というと、新しく[Asset Modules](https://webpack.js.org/guides/asset-modules/#resource-assets)というものが追加になっていました。
この辺り、正しく書いてある記事などがなく、最終的には公式ドキュメントを読み込んで解決しました。

[Asset Modules](https://webpack.js.org/guides/asset-modules/#resource-assets)から引用
> Asset Modules is a type of module that allows one to use asset files (fonts, icons, etc) without configuring additional loaders.
> 
> Prior to webpack 5 it was common to use:
> 
> raw-loader to import a file as a string
> url-loader to inline a file into the bundle as a data URI
> file-loader to emit a file into the output directory
> Asset Modules type replaces all of these loaders by adding 4 new module types:
> 
> asset/resource emits a separate file and exports the URL. Previously achievable by using > file-loader.
> asset/inline exports a data URI of the asset. Previously achievable by using url-loader.
> asset/source exports the source code of the asset. Previously achievable by using raw-loader.
> asset automatically chooses between exporting a data URI and emitting a separate file. Previously achievable by using url-loader with asset size limit.
When using the old assets loaders (i.e. file-loader/url-loader/raw-loader) along with Asset Module in webpack 5, you might want to stop Asset Module from processing your assets again as that would result in asset duplication. This can be done by setting asset's module type to 'javascript/auto'.

翻訳にかけるとこんか感じのことが書かれています

> アセットモジュールとは、アセットファイル（フォント、アイコンなど）を追加のローダーを設定することなく使用できるようにするモジュールの一種です。
> 
> webpack 5以前は、raw-loaderを使用するのが一般的でした：
> 
> raw-loaderは、ファイルを文字列としてインポートします。
> url-loader ファイルをデータURIとしてバンドルにインライン化する。
> file-loader ファイルを出力ディレクトリに出力する。
> Asset Modulesタイプは、これらのローダーをすべて置き換えて、4つの新しいモジュールタイプを追加しています：
> 
> asset/resourceは、別のファイルを出力し、そのURLをエクスポートします。以前はfile-loaderを使用することで実現可能でした。
asset/inlineは、アセットのデータURIをエクスポートします。以前は、url-loaderを使用することで実現可能でした。
asset/source は、アセットのソースコードをエクスポートします。以前は raw-loader を使用することで達成可能でした。
asset は、データ URI をエクスポートするか、別のファイルを生成するかを自動的に選択します。以前は、アセットサイズに制限のあるurl-loaderを使用することで達成可能でした。
webpack 5でAsset Moduleと一緒に古いassets loader (例: file-loader/url-loader/raw-loader) を使用する場合、Asset Moduleがアセットを再度処理しないようにしたいことがあります。これは、アセットのモジュールタイプを「javascript/auto」に設定することで可能です。

すっごい極端に意訳すると「使えるけど使わないことをお勧めするよ:heart:」ということかと。
結果、[公式ドキュメント](https://webpack.js.org/guides/asset-modules/#resource-assets)を参考に以下のように書き換えることで事なきを得ました。

```javascript:webpack.config.js
module: {
  rules: [
    {
      test: /\.(png|jpg|gif|svg)$/,
      type: 'asset/resource',
      generator: {
        filename: '[name][ext]',
        outputPath: 'images/',
        publicPath: () => '/assets/**/images/',
      }
    }
  ]
}
```
---
以上で、無事に`webpack-dev-server`も立ち上がるし、ビルドもきちんとできる環境が整いました。
今回、エラーメッセージをググったりして日本語だけじゃなく英語の記事なども見ましたが
やっぱり最後に頼りになるのは公式ドキュメントでした。（まれにそうじゃない場合もありますが）

また、今回の問題は日本語の記事だけで解決しようとしたら絶対に解決しなかったと思います。
もしくはとんでもなく時間がかかるか…

システムエンジニアとして働いていくには、やっぱり英語は重要だな、と改めて実感させられました。
今回も`webpack`の一部にしか触れていませんが、この記事が誰かの参考になると幸いです。

それでは、またどこかで！