[Laravel7.x]DockerでVue / Vue Router / Vuetify開発環境を構築する

# はじめに
Dockerを用いてモダンな開発環境を手軽に構築する方法をご紹介します。

各ツールのバージョンは以下の通りです。
|PHP|Laravel|Node|Vue.js|Vue Router|Vuetify|
|:---:|:---:|:---:|:---:|:---:|:---:|
|7.4.1|7.30.6|10.15.3|2.5.17|3.1.6|2.6.9|

# 参考サイト
環境構築するにあたり下記サイトが大変勉強になりました。

[最強のLaravel開発環境をDockerを使って構築する - Qiita](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)
[[Laravel7.x]ドキュメントを読みながらVuetifyを導入する - Qiita](https://qiita.com/gentuki/items/c9c3b4c5e3ca81906671)
[Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう (3) SPA開発環境とVue Router | Hypertext Candy](https://www.hypertextcandy.com/vue-laravel-tutorial-setting-up-spa-project)


# 1. Dockerコンテナを立ち上げる
Dockerを用いてPHP、MySQL、nginx、Composer、Node.jsをまとめてインストールします。
Dockerファイル一式は下記リンクから入手できます。

https://github.com/yukimasaki/docker-laravel-separated

[最強のLaravel開発環境をDockerを使って構築する - Qiita](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)を自分流にカスタマイズしています。
Laravelソースコードを別リポジトリで管理できるようになっています。

#### 1. リポジトリをコピーする
まずは、「Use this template」というボタンをクリックして自分のリポジトリにコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140967/395e31a7-de37-a147-e030-5513825e59df.png)

#### 2. 作業用ディレクトリを作成する
```bash
mkdir -p docker-laravel/infra
```

#### 3. リポジトリを複製する
```bash
cd docker-laravel/infra
git clone https://github.com/your-repository/replicated-template.git . # ← 最後のピリオドを忘れないよう注意
```

#### 4. Docker起動 & プロジェクト生成
```bash
make create-project
```

Webブラウザで`http://localhost:8077`へアクセスしWelcomeページが表示されることを確認してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140967/d0279c00-938e-cb92-310b-bc412a4a9034.png)

# 2. Vueをインストール
#### 1. appコンテナに接続する
```bash
make app
```

#### 2. laravel/uiをインストール
```bash
composer require laravel/ui:^2.0 --dev
php artisan ui vue --auth
npm install && npm run dev
```

#### 3. 不要なライブラリを削除
本記事ではCSSフレームワークとしてVuetifyを使用するため、下記の不要なライブラリを削除します。
* Bootstrap
* Jquery
* popper.js

```bash
npm remove bootstrap
npm remove jquery
npm remove popper.js
```

#### 4. 各種ファイルを修正
```scss:resources/sass/app.scss
// Bootstrap
// @import '~bootstrap/scss/bootstrap'; //コメントアウト
```

```js:resources/js/bootstrap.js
//try {
//    window.Popper = require('popper.js').default;
//    window.$ = window.jQuery = require('jquery');
// 
//    require('bootstrap');
//} catch (e) {}
```
※「try ～ catch」までのセクションをコメントアウトします。

#### 5. 動作確認
動作確認を行うため、`welcome.blade.php`を以下のように変更します。
```php:resources/views/welcome.blade.php
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
  <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <meta name="csrf-token" content="{{ csrf_token() }}">
      <link rel="stylesheet" href="{{ mix('css/app.css') }}">
      <title>{{ config('app.name', 'Laravel') }}</title>
  </head>
  <body>
    <div id="app">
      <v-app>
        <example-component></example-component>
      </v-app>
    </div>
    <script src="{{ mix('js/app.js') }}"></script>
  </body>
</html>
```

`npm run dev`コマンドを実行しコンパイルします。

Webブラウザで`http://localhost:8077`へアクセスします。

Example Component
I'm an example component.

と表示されていたらOKです。
（Bootstrapを削除しているので素っ気ないデザインになっていると思いますが問題ありません。）

# 3. Vue Routerをインストール
#### 1. 事前設定
Vue Routerをインストールする前にLaravel側のルーティングを修正しておきます。

```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;

// APIのURL以外のリクエストに対してはindexテンプレートを返す
Route::get('/{any?}', fn() => view('index'))->where('any', '.+');
```

次に、`welcome.blade.php`を`index.blade.php`にリネームします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140967/cb557825-c8dd-58fd-10bc-2df03dc7b282.png)

#### 2. Vue Routerをインストール
```bash
npm install vue-router@3.1
```

#### 3. ルートコンポーネントを作成
まずはルートコンポーネント`resources/js/App.vue`を作成します。
```vue:resources/js/App.vue
<template>
  <div>
    <main>
      <div class="container">
        <RouterView />
      </div>
    </main>
  </div>
</template>
```
上記ルートコンポーネント(App.vue)は、`index.blade.php`の`<div id="app"></div>`に描画されます。

#### 4. ページコンポーネントを作成
まずは、ページコンポーネントの置き場所を作成します。
`resources/js`に`pages`ディレクトリを作成してください。

`pages`ディレクトリ内に以下の2つのファイルを作成します。
※後でVuetifyを導入するため`<template>`の配下に`<v-app>`を記述しています。

`/`へアクセスしたときに表示するページコンポーネントです。
```vue:resources/js/pages/Test1.vue
<template>
    <v-app>
        <h1>Test1</h1>
    </v-app>
</template>
```

`/test2`へアクセスしたときに表示するページコンポーネントです。
動作確認を兼ねてExampleComponent.vueを読み込ませています。
```vue:resources/js/pages/Test2.vue
<template>
    <v-app>
        <h1>Test2</h1>
        <example-component></example-component>
    </v-app>
</template>

<script>
import ExampleComponent from '../components/ExampleComponent.vue'

export default {
    components: {
        ExampleComponent
    },
}
</script>
```

#### 5. ルーティング設定
ルーティングを定義します。
`resources/js/router.js`を作成し以下の内容に変更します。

```js:resources/js/router.js
import Vue from 'vue'
import VueRouter from 'vue-router'

import Test1 from './pages/Test1.vue'
import Test2 from './pages/Test2.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    component: Test1
  },
  {
    path: '/test2',
    component: Test2
  },
]

const router = new VueRouter({
    mode: 'history',
    routes
})

export default router
```

`app.js`を以下の内容に変更します。
```js:resources/js/app.js
import './bootstrap'
import Vue from 'vue';
import router from './router'
import App from './App.vue'

const app = new Vue({
    el: '#app',
    router,
    components: {
        App,
    },
    template: '<App />'
});
```
#### 6. 動作確認
`npm run dev`コマンドを実行しコンパイルします。
ブラウザを再読み込みしてください。

まずは、`http://localhost:8077/`にアクセスしてください。
「Test1」とだけ表示されたページが表示されればOKです。

次に、`http://localhost:8077/test2`にアクセスしてください。
「Test2」と「Example Component～」と表示さればOKです。

# 4. Vuetifyをインストール
#### 1. Vuetifyをインストール
```bash
npm install vuetify
```

#### 2. vuetify.jsを作成
`resources/js`に`plugins`ディレクトリを作成してください。

下記内容で`vuetify.js`を作成します。
```js:resources/js/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'

Vue.use(Vuetify)

const opts = {}

export default new Vuetify(opts)
```

#### 3. app.jsを修正
vuetifyを読み込ませるため、下記の通り`app.js`を修正します。
```js:resources/js/app.js
import './bootstrap'
import Vue from 'vue';
import Vuetify from './plugins/vuetify' //追記
import router from './router'
import App from './App.vue'

const app = new Vue({
    el: '#app',
    vuetify: Vuetify, //追記
    router,
    components: {
        App,
    },
    template: '<App />'
});
```

#### 4. マテリアルアイコンとグーグルフォントを導入
まずはマテリアルアイコンをインストールします。
```bash
npm install @mdi/font
```

`vuetify.js`を修正します。
```js:resources/js/plugins/vuetify.js
import Vue from 'vue'
import Vuetify from 'vuetify'
import 'vuetify/dist/vuetify.min.css'
import '@mdi/font/css/materialdesignicons.css' //追記

Vue.use(Vuetify)

//以下5行を変更
const opts = {
    icons: {
      iconfont: 'mdi',
    },
}

export default new Vuetify(opts)
```

次に、グーグルフォントを導入します。
```scss:resources/sass/app.scss
// @import url('https://fonts.googleapis.com/css?family=Nunito'); //コメントアウト
@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900'); //追記
```

これでVuetifyの導入が完了しました。
`ExampleComponent.vue`を修正して動作確認してみましょう。

```vue:ExampleComponent.vue
<template>
  <v-card
    :loading="loading"
    class="mx-auto my-12"
    max-width="374"
  >
    <template slot="progress">
      <v-progress-linear
        color="deep-purple"
        height="10"
        indeterminate
      ></v-progress-linear>
    </template>

    <v-img
      height="250"
      src="https://cdn.vuetifyjs.com/images/cards/cooking.png"
    ></v-img>

    <v-card-title>Cafe Badilico</v-card-title>

    <v-card-text>
      <v-row
        align="center"
        class="mx-0"
      >
        <v-rating
          :value="4.5"
          color="amber"
          dense
          half-increments
          readonly
          size="14"
        ></v-rating>

        <div class="grey--text ml-4">
          4.5 (413)
        </div>
      </v-row>

      <div class="my-4 subtitle-1">
        $ • Italian, Cafe
      </div>

      <div>Small plates, salads & sandwiches - an intimate setting with 12 indoor seats plus patio seating.</div>
    </v-card-text>

    <v-divider class="mx-4"></v-divider>

    <v-card-title>Tonight's availability</v-card-title>

    <v-card-text>
      <v-chip-group
        v-model="selection"
        active-class="deep-purple accent-4 white--text"
        column
      >
        <v-chip>5:30PM</v-chip>

        <v-chip>7:30PM</v-chip>

        <v-chip>8:00PM</v-chip>

        <v-chip>9:00PM</v-chip>
      </v-chip-group>
    </v-card-text>

    <v-card-actions>
      <v-btn
        color="deep-purple lighten-2"
        text
        @click="reserve"
      >
        Reserve
      </v-btn>
    </v-card-actions>
  </v-card>
</template>

<script>
  export default {
    data: () => ({
      loading: false,
      selection: 1,
    }),
    methods: {
      reserve () {
        this.loading = true
        setTimeout(() => (this.loading = false), 2000)
      },
    },
  }
</script>
```

#### 5. 動作確認
`npm run dev`でコンパイルし`http://localhost:8077/`へアクセスしてみましょう。
Vuetifyの[ドキュメント](https://vuetifyjs.com/ja/components/cards/#props)に掲載されているカードコンポーネントが表示されたらOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1140967/b838f87a-1e83-8954-aec9-f9a33da7f9b7.png)