Vue3 + MDBootstrap / Bootstrap5 のプロジェクトの作成

- [Installation guide](https://mdbootstrap.com/docs/vue/getting-started/installation/)

基本、上に書いてある通りです。ただし、vue のプロジェクト作成方法が少し変わったりして、そのままでは動かなかったので、罠回避のためにメモしておきます。

MDB でない、バニラ（？）Bootstrap5 については末尾に記載。

# いろいろバージョン

```
$ npm -v
9.0.3
$ node -v
v16.16.0
$ yarn -v
1.22.19
```

# プロジェクト作成

以下のサンプルでは project の名前は `sample-bootstrap-project` としています。

```
$ yarn create vue
success Installed "create-vue@3.4.1" with binaries:
      - create-vue

Vue.js - The Progressive JavaScript Framework

✔ Project name: … your-project-name
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Done. Now run:

  cd your-project-name
  npm install
  npm run dev

$ cd your-project-name
$ yarn add mdb-vue-ui-kit
$ yarn
```

この段階では MDBootstrap はまだ使えませんが、`yarn dev` としてページが表示できることを確認しておきます。

```
$ yarn dev
```

`http://localhost:5173/` をブラウザで開くと、下記のようなページが表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/aeacdb03-2f37-99f7-9ae8-00250b33f811.png)


# コードの変更

## main.js

`src/main.js` を下記のように書き換えます。

```src/main.js
import { createApp } from 'vue'
// import './style.css' // これをコメントアウトする！！
import App from './App.vue'
import 'mdb-vue-ui-kit/css/mdb.min.css'; // 追加

createApp(App).mount('#app')
```

`stype.css` をコメントアウトしないと、画面がダークかつ中央寄りになります。

## index.html

下記の二行を `<head>` 以下に追加します。

```index.html
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet" />
    <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700,900&display=swap" rel="stylesheet" />
```

## App.vue

style にフォントを追加します。

```src/App.vue
<style>
#app {
  font-family: Roboto, Helvetica, Arial, sans-serif;
}
</style>
```

これで、あとはコンポーネントを個別に import すればOKです。

# 具体的な例

```src/App.vue
<template>
  <header>
    <MDBNavbar expand="lg" dark bg="dark" container>
      <MDBNavbarBrand href="#">Brand</MDBNavbarBrand>
      
      <MDBNavbarToggler
        @click="collapse1 = !collapse1"
        target="#navbarSupportedContent"
      ></MDBNavbarToggler>
    </MDBNavbar>
  </header>
  <main class="flex-shrink-0 pt-5">
    <div class="container">
      <h1>{{ msg }}</h1>

      <p>
        <a href="https://vitejs.dev/guide/features.html" target="_blank">
          Vite Documentation
        </a>
        |
        <a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
      </p>

      <MDBBtn color="primary" class="mb-3" @click="state.count++">
        count is: {{ state.count }}
      </MDBBtn>

      <p>
        Edit components/HelloWorld.vue to test hot module replacement.
      </p>
    </div>
  </main>
</template>
<script>
import {
  MDBBtn,
  MDBNavbar,
  MDBNavbarToggler,
  MDBNavbarBrand,
} from 'mdb-vue-ui-kit';

export default {
  // 使用するコンポーネントはすべてここに記述する必要がある。
  components: {
    MDBBtn,
    MDBNavbar,
    MDBNavbarToggler,
    MDBNavbarBrand,
  },
  data: () => (
    {
      msg: "Welcome to MDBootstrap",
      state: {
        count: 0
      },
      collapse1: false
    }
  )
}
</script>
<style scoped>
#app {
  font-family: Roboto, Helvetica, Arial, sans-serif;
}
</style>
```

## テスト

```
$ yarn dev
```

これで、`http://localhost:5173/` をブラウザで開くと、下記のような画面になるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ddba8894-8a04-41ca-d8e2-0af7fb7d5730.png)

ボタンを押すと、カウントが進みます。

Vuetify のように、モジュールのオートロードがないので、自分で全部 import する必要があります（多分）。

# Bootstrap5 + Vue3 + Vite

基本、[公式](https://getbootstrap.jp/docs/5.0/getting-started/download/)にある通りです。

```
$ yarn create vue
success Installed "create-vue@3.4.1" with binaries:
      - create-vue

Vue.js - The Progressive JavaScript Framework

✔ Project name: … your-project-name
✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes

Done. Now run:

  cd your-project-name
  npm install
  npm run dev

$ cd your-project-name
$ yarn add bootstrap @popperjs/core bootstrap-icons

```

`bootstrap-icons` は必須ではないです（使わないってことはないと思いますが・・・）

## main.js

```src/main.js
import { createApp } from 'vue'
import App from './App.vue'

//import './assets/main.css'
import 'bootstrap'
import "bootstrap/dist/css/bootstrap.min.css";

const app = createApp(App)

app.mount('#app')
```

`main.css` を必ずコメントアウトします。これであとは、普通に Bootstrap が使えます。

## App.vue

下記は[Sticky footer with fixed navbar](https://getbootstrap.jp/docs/5.0/examples/sticky-footer-navbar/)のコードをほぼそのまま `App.vue` にコピペしただけです。

```src/App.vue
<template>
  <div class="d-flex flex-column h-100">
    <header>
      <nav class="navbar navbar-expand-md navbar-dark bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navbar</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">Link</a>
              </li>
              <li class="nav-item dropdown">
                <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                  Dropdown
                </a>
                <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
                  <li><a class="dropdown-item" href="#">Action</a></li>
                  <li><a class="dropdown-item" href="#">Another action</a></li>
                  <li><hr class="dropdown-divider"></li>
                  <li><a class="dropdown-item" href="#">Something else here</a></li>
                </ul>
              </li>
              <li class="nav-item">
                <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
              </li>
            </ul>
            <form class="d-flex">
              <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
              <button class="btn btn-outline-success" type="submit">Search</button>
            </form>
          </div>
        </div>
      </nav>
    </header>

    <main class="flex-shrink-0">
      <div class="container">
        <h1 class="mt-1">Sticky footer with fixed navbar</h1>
        <p class="lead">Pin a footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS. A fixed navbar has been added with <code class="small">padding-top: 60px;</code> on the <code class="small">main &gt; .container</code>.</p>
        <p>Back to <a href="/docs/5.0/examples/sticky-footer/">the default sticky footer</a> minus the navbar.</p>
      </div>
    </main>
    
    <footer class="footer mt-auto py-3 bg-light">
      <div class="container">
        <span class="text-muted">Place sticky footer content here.</span>
      </div>
    </footer>
    
  </div>
  </template>
  
  <script setup>
  
  </script>
  
  <style scoped>
  main > .container {
    padding: 60px 15px 0;
  }
  </style>
```

結果：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/8aeba6bb-e3b0-a535-967a-317dd0c965e9.png)

## vue-router (おまけ）

```
$ yarn add vue-router
```

src/main.js を以下のように変更する。

```src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

//import './assets/main.css'
import 'bootstrap'
import "bootstrap/dist/css/bootstrap.min.css";

const app = createApp(App)

app.use(router)

app.mount('#app')
```

`/src/router` を作成して `/src/router/index/js` を次のようにします。

```src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router';
// 上の import は必須
import HelloWorld from '../components/HelloWorld.vue';
import HomeView from '../components/HomeView.vue';

// ここは読み込ませるページの内容に依存
// 何もページが無い場合は空の配列を与えておけばとりあえずエラーにはならない。
const routes = [
    {
        path: '/',
        name: 'mainpage',
        component: HomeView
    },
    {
        path: '/hello',
        name: 'hellopage',
        component: HelloWorld
    },
]

// これ以下は必須
const router = createRouter({
    history: createWebHashHistory(),
    routes
})
  
export default router
```

`src/App.vue` の `main` 要素の中に `<router-view></router-view>` を追加する（一例）。

```src/App.vue
<template>
<div class="d-flex flex-column h-100">
<header>
  <nav class="navbar navbar-expand-md navbar-dark bg-dark">
    <div class="container-fluid">
      <span class="navbar-brand">Navbar
      </span>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav me-auto mb-2 mb-lg-0">
          <li class="nav-item">
            <a class="nav-link active" aria-current="page" href="#">Home</a>
          </li>
          <li class="nav-item">
            <router-link class="nav-link active" aria-current="page" to="/hello">Hello</router-link>
          </li>
          <li class="nav-item">
            <button class="btn btn-outline-light" @click="$router.push('/hello')">Hello</button>
          </li>
          <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
              Dropdown
            </a>
            <ul class="dropdown-menu" aria-labelledby="navbarDropdown">
              <li><a class="dropdown-item" href="#">Action</a></li>
              <li><a class="dropdown-item" href="#">Another action</a></li>
              <li><hr class="dropdown-divider"></li>
              <li><a class="dropdown-item" href="#">Something else here</a></li>
            </ul>
          </li>
          <li class="nav-item">
            <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
          </li>
        </ul>
        <form class="d-flex">
          <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
          <button class="btn btn-outline-success" type="submit">Search</button>
        </form>
      </div>
    </div>
  </nav>
</header>
<main class="flex-shrink-0">
  <router-view></router-view>
</main>

<footer class="footer mt-auto py-3 bg-light">
  <div class="container">
    <span class="text-muted">Place sticky footer content here.</span>
  </div>
</footer>
</div>
</template>

<script setup>

</script>

<style scoped>
main > .container {
  padding: 60px 15px 0;
}
</style>
```

`vue-router` で遷移する場合は、`<router-link to="/hoge">HOGE</router-link>` とします。`<button class="btn" @click="$router.push('/hello')">Hello</button>` みたいな方法もありです。

`components/HomeView.vue` の例。

```components/HomeView.vue
<template>
  <div class="container">
    <h1 class="mt-1">Sticky footer with fixed navbar</h1>
    <p class="lead">Pin a footer to the bottom of the viewport in desktop browsers with this custom HTML and CSS. A fixed navbar has been added with <code class="small">padding-top: 60px;</code> on the <code class="small">main &gt; .container</code>.</p>
    <p>Back to <a href="/docs/5.0/examples/sticky-footer/">the default sticky footer</a> minus the navbar.</p>
  </div>
</template>
```

これで、画面遷移が可能になります。

## さらなるおまけ

vue-router で historical mode を使っているときに、リロードしたときに 404 になるのを防ぐ方法について。

- [Vue Routerでリロード/直リンクでの404を回避する](https://www.tsukiyono.blue/blog/2019/11/vue_reload_404.html)

`/public` に `_redirects` というファイルを作り、中身を下記にします。

```/public/_redirects
/*    /index.html   200
```


## もひとつおまけ：Vite の HMR 設定

`vite.config.js` を下記のようにしておけば、コードを更新すると自動的にページの読み込みがなされます。ただし、アイコンや画像などを外部URLから取ってくるようなコードを追加したときは、リロードが必要のようです（多分）

```vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  server: {
    watch: {
      usePolling: true
    }
  },
  plugins: [vue()]
})
```