Vuetify3のコンポーネントでもパスのエイリアスを使いたい

### 環境

- Vue3
- Vite
- Vuetify3
- TypeScript

### TL;DR

[vite.config.tsにv-XXXタグを追加する](#対応)

### Vueプロジェクトのパスのエイリアス

例えばVueプロジェクトで、`<img>`タグで`src/assets`フォルダに配置した画像を表示したい場合、以下のようにファイルからの相対パスで記載することができます。

```vue
<img src="../assets/header_image.jpg">
```

ただしこの表記はファイルの位置によって`..`の数を変えないといけないため面倒です。

そこでパスのエイリアスを利用すると便利です。プロジェクトを立ち上げると、ルートのsrcフォルダを示すエイリアス`@`が設定されています。これにより以下のように記述することができ、ファイルの位置を気にせず同じ表記で画像を参照できます。

```vue
<img src="@/assets/header_image.jpg">
```

これは`vite.config.ts`で定義されており、`@`が`./src`を示していることがわかります。

```vite.config.ts
import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [
    vue(),
  ],
  resolve: {
  // @をエイリアスとして設定している
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

```

### Vuetifyのコンポーネント属性

[Vuetify](https://vuetifyjs.com/en/)はVue.jsで使用できるUIフレームワークで、簡単な記述でマテリアルデザインのコンポーネントを作ることができます。（詳しくはVuetifyのリファレンスを参照ください）

Vuetifyコンポーネントには、画像を参照するコンポーネントがいくつかあります。例えば[`<v-img>`](https://vuetifyjs.com/en/api/v-img/#links)や[`<v-carousel-item>`](https://vuetifyjs.com/en/api/v-carousel-item/)です。これらはPropsで親コンポーネントからsrcを受け取り、画像を表示します。

```vuejs
<v-carousel>
  <v-carousel-item
    src="https://cdn.vuetifyjs.com/images/cards/docks.jpg"
    cover
  ></v-carousel-item>
</v-carousel>
```

ここで、v-carousel-itemで`assets`配下の画像を設定したいとします。しかし以下のように`@`エイリアスを使用すると画像が表示できません。

```vuejs
<v-carousel>
  <v-carousel-item
    src="@/assets/header_image.jpg"
    cover
  ></v-carousel-item>
</v-carousel>
```

Googleデベロッパーツールを使って、HTMLやコンソールのエラーを見ると

```html
<img class="v-img__img v-img__img--cover" src="@/assets/home_1.jpg" style="display: none;">
```

```
GET http://localhost:5173/@/assets/home_1.jpg 404 (Not Found)
```

と表示されており、`@`が`.src`からのパスに変換できていないようです。

### 対応

これは、viteはsrcに指定しているパスの変換をコンパイル時に行いますが、変換対象のタグや属性がデフォルトで指定されているためです。

https://github.com/vitejs/vite-plugin-vue/blob/main/packages/plugin-vue/README.md#asset-url-handling

```typescript
{
  video: ['src', 'poster'],
  source: ['src'],
  img: ['src'],
  image: ['xlink:href', 'href'],
  use: ['xlink:href', 'href']
}
```

v-imgやv-carousel-itemといったタグは変換対象ではないので、コンパイル時に`@`が変換されないようです。

そこで、`vite.config.ts`を編集して変換対象のタグを追加します。`template.transformAssetUrls`に対象のタグを記述します。

```typescript
export default defineConfig({
  plugins: [
    vue({
    //以下を追加
      template: {
        transformAssetUrls: {
          tags: {
            'v-img': ['src'],
            'v-carousel-item': ['src'],
            video: ["src", "poster"],
            source: ["src"],
            img: ["src"],
            image: ["xlink:href", "href"],
            use: ["xlink:href", "href"],
          }
        }
      }
    }
    ),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})
```

こうすることで、Vuetifyコンポーネントでも`@`を記述することができます。

```vuejs
<v-carousel>
  <v-carousel-item
    src="@/assets/header_image.jpg"
    cover
  ></v-carousel-item>
</v-carousel>
```

### おまけ

VuetifyコンポーネントのPropsのsrcに相対パスを記述することはできないようです。

`@`を使わない場合、以下の記述で画像を表示できます。

1.ルートディレクトリからの絶対パスを書く

```vuejs
  <v-carousel>
    <v-carousel-item src="/src/assets/header_image.jpg" cover></v-carousel-item>
  </v-carousel>
```

2.相対パスをimportしてから使う

```vuejs
<script setup lang="ts">
    import header from '../assets/header_image.jpg'
</script>

~中略~
  <v-carousel>
    <v-carousel-item v-bind:src="header" cover></v-carousel-item>
  </v-carousel>
```

### 参考

- https://stackoverflow.com/questions/76846334/automatically-import-image-from-relative-path-in-vuetify3s-v-img-component

- https://azukiazusa.dev/blog/vite-require/