【MPA】LarvelにVueコンポーネントを導入する方法とVueコンポーネントが大量にある時のコツ💡

以前SPAとしてVueをフロントエンド、Laravelをバックエンドとして利用することは多々あったが、MPAでLaravelのBlade表示のためにVueコンポーネントを利用するという経験がなかったので、その時のことをまとめる。
## 環境
MacOS Catalina
Vue 2.6
Laravel 8.0

## SPAとMPAのイメージ
これまでやってきたSPA

![SPA.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/926931/2b397a4b-f64d-8a98-9a0d-650be7430963.jpeg)
初回のHTTPリクエストで返ってきたHTMLに対してコンテンツの切り替えのみを行うため、ページ遷移を行わずより高速な画面更新が可能になる。
完全にフロントエンドとバックエンドをVueとLaravelで分けて、Vueでaxiosを利用してLaravelのAPIを叩く形で利用。
LaravelはAPIとしての責任を持たせ、それ以外はVue側に責任を持たせるような実装である。

今回挑戦したMPA
![MPA.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/926931/cdbe522f-f2bc-88f3-4a3a-ca711cb9e2d6.jpeg)

リクエストごとに毎回サーバー側でHTMLがレンダリングされて表示される。
LaravelのBladeにVueコンポーネントをマウントしてフロントの表示を行う。ルーティング、コントローラーは通常のLaravelのように利用し、APIを叩く際は別途用意したAPIサーバをaxiosでリクエストして利用した。

## Vueコンポーネントを利用するまでのステップ
①Vueのインストール
②Vueコンポーネントタグの登録
③BladeにVueコンポーネントを読み込む

## ①Vueのインストール
まずは、通常通りLaravelのプロジェクトをインストールする。
その後、それぞれlaravel/uiとvue.jsをインストールを行う。

```:ターミナル
$ composer require laravel/ui
$ php artisan ui vue
```

次に、package.jsonを確認します。

```:pacage.json
“vue”: “^2.5.17”
“vue-template-compiler”: “^2.6.10”
```

このように必要なVueのパッケージが記述されて、vueのインストールができる状態になっている確認しなければならない。

次に、 npmコマンドでVueをインストールする。

```:ターミナル
$ npm install
```

これにより、node_modulesがインストールされ、Vueを利用する準備ができた。

## ②Vueコンポーネントタグの登録

### webpackでの読み込み
webpack.mix.jsにVueが毎回コンパイルされるような記述を加える。

```js:webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .vue()
```

### app.jsへのVueコンポーネントの設定
次に、`resources/js/app.js`に利用するVueコンポーネントを記述していく。
下記はデフォルトの記述を参考にしている。

```js:app.js
require('./bootstrap');

window.Vue = require('vue');

Vue.component('example-component', require('./components/ExampleComponent.vue').default);

const app = new Vue({
    el: '#app',
});
```

Vueコンポーネントを呼び出している部分は以下の部分である。
```js:app.js
Vue.component('example-component', require('./components/ExampleComponent.vue').default);
```

これは厳密には以下のように分けられる。
```js:app.js
Vue.component('タグ', require('パス').default);
```
タグがLaravelのBlade側でコンポーネントを呼ぶ際の名称でパスがコンポーネント用のファイルが存在する相対パスである。

この記述により、Vue側の準備は整った。あとは適宜表示するVueコンポーネントの編集をBladeで表示することを想定して行う必要がある。

## ③BladeにVueコンポーネントを読み込む
最後に作成したVueコンポーネントをBladeから呼び出し表示する。
そのためには、いくつかBlade側で設定を行う必要がある。

### CSRFトークンの挿入

Laravelではセキュリティの関連からVue.jsを利用する場合、CSRFトークンを追加することが推奨されている。

標準で備わっているので簡単に設定できる。

```php:〇〇.blade
<meta name="csrf-token" content="{{ csrf_token() }}">
```

### CSSやJSの読み込み
mix関数を利用すると、Vue.jsを追加した際のresourcesとpublicの２つのフォルダにそれぞれ存在するapp.cssを適当に組み合わせて表示してくれる。
app.jsも同様です。

```php:〇〇.blade
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
<script src="{{ mix('js/app.js')}}"></script>
```

また、あらかじめpublicの方に統一してapp.cssやapp.jsを記述している場合はasset関数を利用して以下のように記述する方法もある。

```php:〇〇.blade
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
<script src="{{ asset('js/app.js') defer  }}" ></script>
```
deferを記述することでHTML、ここではBladeのテンプレートの読み込み後にscriptを読み込むため、headタグ内に書くことも可能。

※webpack.mix.jsでコンパイルしているため、`resources/js/app.js`は自ずと読み込まれる。そのため、Vueの設定ファイルに`resources/js/app.js`、各スクリプトやページの共通設定に`public/js/app.js`を記述してもいいかもしれない。


#### 💡Point
また、これらのBlade側の設定はそれぞれのBladeで共通設定であれば、 layoutに記述するほうが共通化できる。
*****

### Vueコンポーネントの表示
ここまできたらBladeの設定もできたので、②で登録したVueのコンポーネントタグを記述するのみである。
以下のようにBlade側で設定する。

```php:〇〇.blade
<div id="app">
    <example-component></example-component>
<div> 
```

そして、プロジェクトのビルドを行ってからLaravelのサーバーを立ち上げる。

```:ターミナル
$ npm run dev
$ php artisan serve
```
作成したVueコンポーネントが表示したいBladeで表示されていたら完了。

# Vueコンポーネントが大量にある時のコツ💡
Vueコンポーネントがたくさんある際②のステップで一つ一つのVueコンポーネントをapp.jsに記述するのは面倒である。

そのような時は別ファイルにVueコンポーネントのタグやパスを記述して読み込めるようにすると責任の細分化と可読性を上げた記述が可能になる。
具体的には、vue_route.jsのようなファイルを作成し、必要なVueコンポーネントの情報を記述する。そして、そのファイルをapp.jsでimportしてループ文で読み込むようにする。

実際に以下のステップで行う。

①vue_route.jsの作成
②vue_route.jsをapp.jsで読み込む

## ①vue_route.jsの作成
今回はcomponentsというフォルダを作成して、そこにvue_route.jsの作成と利用するVueコンポーネントを作成する。

例として、postsとcommonというディレクトリを作成して、そこに利用するvueコンポーネントを作成した。これらのVueコンポーネントをvue_route.jsを利用して、app.jsに記述していく。

```:resources/js
js
├── app.js
└──components
   ├── common
   │   ├── TitleText.vue
   │   └──buttons
   │      ├── DefaultBackButton.vue
   │      └── DefaultSubmitButton.vue
   │ 
   ├── posts
   │   ├── Index.vue
   │   └── Detail.vue
   │   
   └── vue_routes.js
```

②で紹介した以下の必要なVueコンポーネント情報を振り返ると、app.jsではタグとパスの情報が必要である。
```js:app.js
Vue.component('タグ', require('パス').default);
```

実際にvue_route.jsはapp.jsでループ文を利用するため、配列でVueコンポーネントの情報を保存する。

```js:vue_route.js
export default [
    {tag: 'posts-index', dir: './components/posts/', file: 'Index.vue'},

    {tag: 'posts-detail', dir: './components/posts/', file: 'Detail.vue'},
]
```

commonディレクトリ内のVueコンポーネントはこのpostsディレクトリ内のVueコンポーネントから読み込む前提であるため、vue_route.jsには記述しない。
Bladeから直接読み込むpostsディレクトリのVueコンポーネントをVue_route.jsに記述する。

必要な情報であるタグとパスについてはタグとディレクトリ、ファイル名に分割している。
パスはディレクトリとファイル名で構成するように記述した。

## ②vue_route.jsをapp.jsで読み込む
vue_route.jsを作成したらこのファイルをapp.jsで利用できるようにimportする。
そして、importしたVueコンポーネントの情報をfor文を利用して、読み込んでいく。

```js:app.js
//vue_route.jsの読み込み
import vue_routes from "./components/vue_routes"; 

require('./bootstrap');

window.Vue = require('vue');

// for文でvue_route.jsからVueコンポーネントを読み込む
for (var i in vue_routes) {
    var tag = vue_routes[i].tag;
    var dir = vue_routes[i].dir;
    var file = vue_routes[i].file;

    Vue.component(tag, require('' + dir + file).default);
}

const app = new Vue({
    el: '#app',
});

```

今回はBladeに直接表示するVueコンポーネントが2つしかないが、数が多ければ多いほどこの記述によって一つ一つVueコンポーネントを記述する方法より簡潔に記述できる。
あとの過程は最初の方法と同じである。

このように、Bladeで直接呼ぶVueコンポーネントが多い時は最初の工程で紹介した②**Vueコンポーネントタグの登録**の過程を少し変更するだけで、大きく記述を簡略化、責任の細分化ができるだろう。