サブディレクトリでの公開サイトでwebpackを使っていたら、cssの画像パスにはまった件

##はじめに
vueCLIにてwebpackを使った開発をしている際、GitHubPagesを使ってサイト公開していた関係で、
サブディレクトリへのデプロイをしていたところ、cssに定義した画像パス（background-imageとか）に悩まされたのでその経緯と解決方法を残しておこうと思います。

##前提
- vue-cli
- 公開先は`http://domain/`の様なルートディレクトリではなく`http://domain/hoge/`といったサブディレクトリ
- 本番（`http://domain/hoge`）と検証用（`http://domain/hoge/docs`）で公開するサブディレクトリの階層が異なる
- 上の二点により、donfig/index.jsは以下の形に変更済み

```js:config/index.js
  build: {
    index: path.resolve(__dirname, '../docs/index.html'),

    assetsRoot: path.resolve(__dirname, '../docs'),
    assetsSubDirectory: 'static',
    assetsPublicPath: '',
```

##経緯
webpackをインストールした時、App.vueに以下の記述がありました。

```html:src/App.vue
   <img src="./assets/logo.png">
```
ビルド後はこの様に自動的に変換してくれます。

```html:index.html
   <img src="static/logo.png">
```
このことにより、`assets`は画像を格納する場所、かつvueからはassetsへの相対パスを発行すると思っていました。
しかし、src/components/xxxxx.vueから画像を参照しようと思った時にビルド前とビルド後で相対パスが変わってしまうことに気づきます。
設定内容とファイル構成は以下の通りです。

```html:src/components/xxxxx.vue
<div class="top">
...
<style>
.top {
    background-image: url("../assets/test.jpg"); //componentsから見たassetsへの相対パス
</style>
```
```:ビルド後のファイル構成
PROJECT_ROOT
  ┣ docs
    ┣ static
      ┣ css
        ┣ app.xxxxxxx.css //background-image: url("static/img/test.jpg");
      ┣ img
        ┣ test.jpg
      ┣ js
    ┣ index.html
```
一見、問題なさそうなのですが、デプロイすると画像が404となっています。
その時の画像のURLは以下です。
`http://domain/hoge/docs/css/static/img/test.jpg`
そう、cssからの相対パスなので、css配下のstatic/imgを読みにいってしまっているのです。
当然cssの配下にstaticはありませんので、404です。

これは、サブディレクトリではなく、ルートディレクトリにしていたら起こりえません。


```html:変換された後のcssの定義：今回（config/index.jsでstaticを定義）
<style>
 background-image: url("static/img/test.jpg");
</style>
->画像へのパス：http://domain/hoge/docs/css/static/img/test.jpg
```

```html:変換された後のcssの定義：config/index.jsを/staticのママにしていた場合
<style>
 background-image: url("static/img/test.jpg");
</style>
->画像へのパス：http://domain/static/img/test.jpg
```
後者の場合、cssからの画像参照であってもルートディレクトリからの絶対パスになるので問題になりません。

##解決方法
webpackへの理解が甘く、2日くらいはまってしまったのですが、結論として以下を諦めることにしました。

- 本番（`http://domain/hoge`）と検証用（`http://domain/hoge/docs`）で公開するサブディレクトリの階層が異なる

全て叶える方法はいくつか見つけましたが、どれもフレームワークを使っているとは思えない構造になってしまい、
一般的とはどうしても思えなかったからです。
一応、全てを実現する形の対応方法は、[おまけ](#おまけ)に記載しています。

それでは、残る問題であるこちらの対応をしていきます。

- 公開先は`http://domain/`の様なルートディレクトリではなく`http://domain/hoge/`といったサブディレクトリ

###具体的な対応方法

- 編集ファイル：config/index.js　※バージョンによって違うファイル名の物を編集する必要があることがあります。
- 画像ファイル格納場所：src/assets/test.jpg

```js:config/index.js
  build: {
    // assetsPublicPath: '',
    assetsPublicPath: '/hoge/docs/',
    ...
```
```html:src/components/xxxxx.vue
<div class="top">
...
<style>
.top {
    background-image: url("../assets/test.jpg");
</style>

```:ビルド後のファイル構成
PROJECT_ROOT
  ┣ docs
    ┣ static
      ┣ css
        ┣ app.xxxxxxx.css //background-image: url("/hoge/docs/static/img/test.jpg");
      ┣ img
        ┣ test.jpg
      ┣ js
    ┣ index.html
```
無事画像が表示されました。
その時の画像のURLは当たり前ですが以下です。
`http://domain/hoge/docs/static/img/test.jpg`
リリースディレクトリの違いを明らめたということは絶対パスで良いということですね。
逆にリリースディレクトリが違わないのであれば、安易に相対パスにすることはやめた方がいいです。

##おまけ
私は選択しませんでしたが、以下は当初の前提を全て動かすことなく解決できた方法なのでご参考までに。
特に２つ目は、ほとんどwebpackの恩恵を受けられないものの「staticフォルダ」が存在している以上、利用しても問題ないと思われます。

###HTMLタグの中に直接styleを定義し、staticのパスを記述
- 編集ファイル：src/components/xxxxx.vue
- 画像ファイル格納場所：static/img/test.jpg

```html:src/components/xxxxx.vue
<div style="background-image: url('static/img/test.jpg');">
```
```:ビルド後のファイル構成
PROJECT_ROOT
  ┣ docs
    ┣ static
      ┣ css
        ┣ app.xxxxxxx.css
      ┣ img
        ┣ test.jpg
      ┣ js
    ┣ index.html //<div style="background-image: url('static/img/test.jpg');">の記述
```
`<div`タグの中のstyleはcss-loaderの対象外のため、ulr変換されていないのがわかります。
`<div>`タグにstyleを直接記述することそのものが推奨されていません。
なお、assetsを参照していないため、assets配下に画像を入れても自動的には読み込まれないのでstaticに配置しています。

###css-loaderのurl変換をしない
上の手法は無理やりloaderを通さない方法ですが、こちらは明示的にloaderにおいて、urlを変換しないオプションを定義します。
参考：[[webpack]尖りすぎない人のwebpack設定 SASS/SCSS編](http://www.kantenna.com/pg/2018/05/webpack_scss.php)

- 編集ファイル：build/util.js　※バージョンによって違うファイル名の物を編集する必要があることがあります。
- 編集ファイル：src/components/xxxxx.vue
- 画像ファイル格納場所：static/img/test.jpg

```js:build/util.js
exports.cssLoaders = function (options) {
  options = options || {}

  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap,
      url: false //urlを変換しないオプションを定義（行を追加）
    }
  }
```
```html:src/components/xxxxx.vue
<div class="top">
...
<style>
.top {
    background-image: url("../img/test.jpg");
</style>
```
```:ビルド後のファイル構成
PROJECT_ROOT
  ┣ docs
    ┣ static
      ┣ css
        ┣ app.xxxxxxx.css //background-image: url("../img/test.jpg");
      ┣ img
        ┣ test.jpg
      ┣ js
    ┣ index.html
```
先の手法もそうなのですが、vueファイルの中で以下の記述が混在します。

```html:src/components/xxxxx.vue
// cssの記述
<style>
    background-image: url("../img/test.jpg");
</style>

//loaderに任せるHTMLの記述＋画像はassets配下へ格納
<img src="../assets/test.jpg">

//cssの記述に合わせたHTMLの記述＋画像はstatic/img配下へ格納
<img src="static/img/test.jpg">
```
本番と検証でURLの公開ルートが異なりアセットを相対パスで指定したいというを叶えるためにはこれがベストだったと思いますが、
自身としては、これがフレームワークのあるべき姿とは思いづらかったです。

###アセットの相対パスをやめ検証と本番のディレクトリ違いは人力でなんとかする
解決方法とほぼ同じですが、こちらは諦めなかった場合の対応です。

- 編集ファイル：config/index.js　※バージョンによって違うファイル名の物を編集する必要があることがあります。
- 画像ファイル格納場所：src/assets/test.jpg

```js:config/index.js
  build: {
    // assetsPublicPath: '',
    assetsPublicPath: '/hoge/docs/',　// gh-pagesリリース時パス
    /*
    assetsPublicPath: '/hoge/', // masterリリース時パス
     */
    ...
```
```html:src/components/xxxxx.vue
<div class="top">
...
<style>
.top {
    background-image: url("../assets/test.jpg");
</style>

```:ビルド後のファイル構成
PROJECT_ROOT
  ┣ docs
    ┣ static
      ┣ css
        ┣ app.xxxxxxx.css //background-image: url("/hoge/docs/static/img/test.jpg");
      ┣ img
        ┣ test.jpg
      ┣ js
    ┣ index.html
```
```:検証リリース時手順書
1.config/index.jsをassetsPublicPathの「gh-pagesリリース時パス」が有効になっていることを確認
2.config/index.jsをassetsPublicPathの「masterリリース時パス」が無効になっていることを確認
3.config/index.jsをassetsPublicPathの「masterリリース時パス」が無効になっていなければコメントアウトして保存
4.npm run buildにてビルド
```
```:本番リリース時手順書
1.config/index.jsをassetsPublicPathの「masterリリース時パス」が有効になっていることを確認
2.config/index.jsをassetsPublicPathの「gh-pagesリリース時パス」が無効になっていることを確認
3.config/index.jsをassetsPublicPathの「gh-pagesリリース時パス」が無効になっていなければコメントアウトして保存
4.npm run buildにてビルド
```


##おわりに
今回は、当初のやりたいことを一つ諦める形となりました。
あまり一般的ではないことをやろうとしていたことをやらしてくれないのもフレームワークの良さだと思います。
少なくとも現在は、相対パスでアセットを指定する思想ではないと理解しました。