ViteでVue3+TypeScript+TailwindCSS+axiosの雛形アプリを作ってみる

# 目的
Viteでvue3とTailwind.cssの導入をやってみます。

[Tailwind CSS with Vite](https://tailwindcss.com/docs/guides/vite)
の導入そのままですが、TypeScriptにしているのと、実際に生成すると一部は追記するような形になります。

それだけだと、つまらないので以前[Nuxt3でHeadlessCMS](https://qiita.com/aoyagikouhei/items/f9c7e61142efa2fd34a0)という記事で書いたHeadlessCMSのAPIを呼び出して表示してみます。

# 導入
```
npm create vite@latest my-vue-app -- --template vue-ts
cd my-vue-app
npm install
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```

```javascript:tailwind.config.cjs
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

```css:style.css
@tailwind base;
@tailwind components;
@tailwind utilities;

// ...以降元のCSS
```

```vue:App.vue
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
<!-- ↓↓↓↓↓↓↓↓↓↓追加分↓↓↓↓↓↓↓↓↓↓ -->
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
<!-- ↑↑↑↑↑↑↑↑↑↑追加分↑↑↑↑↑↑↑↑↑↑ -->
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

```

```
npm run dev
```

これでTailwindCSSを利用したサンプルが表示されます。

# HeadlessCMS
HeadlessCMSを使ってブログの一覧を表示するコンポーネントを作っていきます。

まずは通信するライブラリaxiosを読み込みます。

```
npm install axios
```

モデルや通信部分を作成していいきます。

```typescript:models/Blog.ts
import { Content } from "./Content";

export interface Blog {
    contents: Content[],
    totalCount: number,
    offset: number,
    limit: number,
}
```

```typescript:models/Content.ts
import { Category } from "./Category";
import { Eyecatch } from "./Eyecatch";

export interface Content {
    id: string,
    createdAt: string,
    updatedAt: string,
    publishedAt: string,
    revisedAt: string,
    title: string,
    content: string,
    eyecatch: Eyecatch,
    category: Category,
}
```

```typescript:models/Category.ts
export interface Category {
    id: string,
    createdAt: string,
    updatedAt: string,
    publishedAt: string,
    revisedAt: string,
    name: string,
}
```

```typescript:models/Eyecatch.ts
export interface Eyecatch {
    url: string,
    height: number,
    width: number,
}
```

```typescript:services/BlogService.ts
import axios from "axios";
import { Blog } from "../models/Blog";

class BlogService {
    private URL = "https://xxxxx.microcms.io/api/v1/blogs"

    public async getBlog(): Promise<Blog> {
        const res  = await axios.get<Blog>(this.URL, {headers: {
            "X-MICROCMS-API-KEY": "xxxxxxxxxx"
        }})
        return res.data
    }
}

export default new BlogService()
```

タイトルを表示するコンポーネント

```vue:components/BlogView.vue
<script lang="ts">
import { defineComponent } from 'vue'
import blogService from '../services/BlogService'

export default defineComponent({
  name: 'BlogView',
  async setup(props) {
    const blog = await blogService.getBlog()
    return {
      blog,
    }
  }
})
</script>

<template>
  <h1>{{ blog.totalCount }}</h1>
  <ol>
    <li v-for="item in blog.contents" :key="item.id">{{item.title}}</li>
  </ol>
</template>
```

コンポーネントを組み込みました。追加したのはSuspenseのタグの部分です。

```vue:App.vue
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
import HelloWorld from './components/HelloWorld.vue'
import BlogView from './components/BlogView.vue';
</script>

<template>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
  <h1 class="text-3xl font-bold underline">
    Hello world!
  </h1>
  <Suspense>
    <template #default>
      <BlogView/>
    </template>
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

<style scoped>
.logo {
  height: 6em;
  padding: 1.5em;
  will-change: filter;
}
.logo:hover {
  filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
  filter: drop-shadow(0 0 2em #42b883aa);
}
</style>
```

# まとめ
ほら簡単でしょ、といいたいところですがmodelsのinterfaceを書くところが面倒くさかったです。本家がそれっぽいライブラリを用意しているっぽいのでそっちを使った方が良いでしょう。

また、最初はSuspenseを使わないで表示しようとしていたら、まったく表示されなくって悩みました。理解できれば、驚くほど便利な機能だと思いました。

これでちょっとしたサイトを作るのに、導入のテンプレートとして利用できそうです。