この連載記事では、フロントエンドに Vue.js + Vue Router + Vuex とサーバーサイドに Laravel を使用したシングルページ Web アプリケーションの開発方法を紹介します。実際に写真共有アプリを開発する手順を通して SPA 開発のエッセンスを学ぶことができます。
今回のチュートリアルで扱うツールなどのバージョンは以下の通りです。
Node | npm | Vue.js | Vue Router | Vuex | PHP | Laravel |
---|---|---|---|---|---|---|
10.15 | 6.4 | 2.6 | 3.1 | 3.1 | 7.4 | 6.9 |
この章からコードを書いていきます。
まずは SPA プロジェクトの作成から Vue Router の導入まで進みます。
Laravel プロジェクトを作成する
まずはプロジェクトを作成します。
私は Mac マシンを使っているので Laravel Valet で開発環境を作成しましたが、Homestead や Docker などの方法もあります。
ここでは、参考までに Docker を用いた開発環境の構築方法を紹介しておきます。
Docker で開発環境構築
以下のディレクトリ構成を作成します。
/vuesplash
├─ /database # コンテナ内のデータ保存先
├─ /web # アプリケーションコード
├─ Dockerfile
├─ docker-compose.yml
└─ install-composer.sh
Dockerfile
FROM php:7.4.1-fpm
COPY install-composer.sh /
RUN apt-get update \
&& apt-get install -y wget git unzip libpq-dev \
&& : 'Install Node.js' \
&& curl -sL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get install -y nodejs \
&& : 'Install PHP Extensions' \
&& docker-php-ext-install -j$(nproc) pdo_pgsql \
&& : 'Install Composer' \
&& chmod 755 /install-composer.sh \
&& /install-composer.sh \
&& mv composer.phar /usr/local/bin/composer
WORKDIR /var/www/html/vuesplash
docker-compose.yml
version: '3'
services:
vuesplash_web:
build: .
volumes:
- ./web:/var/www/html/vuesplash
ports:
- 8081:8081
- 3000:3000
vuesplash_database:
image: postgres:11-alpine
restart: always
environment:
POSTGRES_PASSWORD: secret
volumes:
- ./database:/var/lib/postgresql/data
ports:
- 5434:5432
install-composer.sh
#!/bin/sh
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
起動
Docker を起動します。docker-compose.yml
があるディレクトリで実行してください。
$ docker-compose up -d
コンテナ内にログインします。
$ docker-compose exec vuesplash_web bash
Laravel プロジェクトを作成します。
$ composer create-project --prefer-dist laravel/laravel .
開発用サーバーを立ち上げます。
$ php artisan serve --host 0.0.0.0 --port 8081
ホスト側から実行するのであれば、以下のコマンドになります。
$ docker-compose exec vuesplash_web php artisan serve --host 0.0.0.0 --port 8081
本章の後半で npm
コマンドを実行しますが、その場合も artisan
コマンドと同様です。
$ docker-compose exec vuesplash_web npm install
$ docker-compose exec vuesplash_web npm run watch
コンテナ停止
コンテナの停止コマンドは以下の通りです。
$ docker-compose stop
Laravel の初期設定
app.php
config/app.php
の locale
設定を日本語にします。
'locale' => 'ja',
.env
アプリ名およびデータベース接続情報を書き換えます。
APP_NAME=Vuesplash
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=vuesplash
DB_USERNAME=postgres
DB_PASSWORD=postgres
冒頭の設定を使用して Docker で開発している場合は、データベース接続情報は以下の通りになります。vuesplash_database
は docker-compose.yml
で指定したサービス名です。
DB_CONNECTION=pgsql
DB_HOST=vuesplash_database
DB_PORT=5432
DB_DATABASE=postgres
DB_USERNAME=postgres
DB_PASSWORD=secret
EditorConfig
EditorConfig の設定は好みによりますが、私は PHP 以外のファイルはインデントをスペース2つにしたいので、最後の2行の拡張子を以下のように書き換えました。
[*.{yml,json,scss,html,js,vue,blade.php}]
indent_size = 2
ここまでの設定で http://vuesplash.test にアクセスし、Laravel の welcome 画面が表示されることを確認しておいてください。
フロントエンドの準備
次に JavaScript と SCSS のセッティングを行います。
JavaScript
次のコマンドでパッケージをインストールします。
$ npm install
Vue.js を追加でインストールします。
$ npm install -D vue
スタイルシート
イントロダクションで述べたとおり、スタイルシートのコーディングはこのチュートリアルの範疇外とします。S3 にアップロードした CSS をあとでテンプレートから読み込みます。
もとの Scss ファイルはこちら(sass.zip)に置いてあるので、関心がある方はダウンロードして見てみてください。
ちなみにレスポンシブには対応できていませんのであしからず
Laravel Mix
Laravel では JavaScript や Vue コンポーネント、SCSS などをコンパイルする仕組みが最初から備わっています。そのため、Gulp や Webpack などの煩雑な設定作業を行う必要がありません。
具体的には Webpack でフロントエンドをビルドしますが、設定を容易にするために Laravel Mix というライブラリを用います。
例えば JavaScript をコンパイルするための設定はたったこれだけです
mix.js('resources/js/app.js', 'public/js')
ビルドの際に設定ファイル webpack.mix.js
が参照され、Webpack の設定が動的に生成されます。自分で Webpack の設定ファイルを書くより遥かに簡単な記述で済んでしまいますね。
では webpack.mix.js
を以下の通り編集してください(*1)。
const mix = require('laravel-mix')
mix.browserSync('vuesplash.test')
.js('resources/js/app.js', 'public/js')
.version()
一つずつ何をしているか紹介していきます。
browserSync
BrowserSync というツールを組み合わせて、JavaScript や PHP ファイルが変更されたときに自動的にブラウザがリロードされるようになります。
Docker コンテナで実行する場合は以下の引数を指定してください。
mix.browserSync({
proxy: '0.0.0.0:8081', // アプリの起動アドレス
open: false // ブラウザを自動で開かない
})
js
JavaScript と Vue コンポーネントをコンパイルします。
第一引数がコンパイル対象のファイル、第二引数がコンパイル結果の配置先です。
version
コンパイルしたファイルのバージョニングが有効になります。
普通ブラウザは一度取得したファイルをキャッシュに保存するので、ファイルの内容を変更してもキャッシュが効いてしまってページに反映されないことがあります。
バージョニングを有効にすると、ビルドするたびにコンパイルしたファイルの URL にランダムな文字列を付けてブラウザがキャッシュを読まないようにします。
この機能はテンプレート側で mix
関数と組み合わせて使います。
<script src="{{ mix('js/app.js') }}" defer></script>
上のテンプレートは、以下のような HTML に変換されます。
<script src="/js/app.js?id=87459a9d906e11120dd5" defer=""></script>
クエリパラメータ id
が付与されていますね。id
の値はランダムに決まるのですが、要するにコンパイルのたびに URL が変わるのでブラウザからは以前とは異なるリクエストと見なされてキャッシュを読まずにサーバとの通信を行います。
これによってキャッシュのせいで変更が反映されない!という現象が起こらなくなります。
Laravel Mix についてより詳しくはマニュアル( 公式 / 日本語)を参照してください。
以上でセッティングは完了したので、いよいよコーディングに入ります。
画面(HTML)を返す
まずは画面をレスポンスするコードから書いていきましょう。
以下の図で言うと最初の「画面要求」〜「画面(HTML)返却」を実装することになります。
ルーティング
routes/web.php
を以下の内容に編集します。
<?php
// APIのURL以外のリクエストに対してはindexテンプレートを返す
// 画面遷移はフロントエンドのVueRouterが制御する
Route::get('/{any?}', fn() => view('index'))->where('any', '.+');
※上記は PHP 7.4 から導入されたアロー関数の文法を用いています。
コメントにもありますが、前章で設計した API の URL 以外の URL へのリクエストに対しては常に特定の HTML を返却する必要があります。
例えば /login
という URL にアクセスがあったときでも、サーバからは上述のコードの通り index テンプレートを返却します。/login
というパスに対応したコンテンツを表示するのはあくまでフロントエンド(特に Vue Router)の役割です。
{any?}
で任意のパスパラメータ any
を受け入れています。次に where
メソッドで正規表現を用いて any
パスパラメータの文字列形式を定義していますが、.+
で「任意の文字が一文字以上」つまり「なんでもいい」という指定をしています。
まとめると、any
パラメータはあってもなくてもいい(?
)し、ある場合はどんな文字列でもいい(.+
)ということになります。
この記述によって、すべての URL で index テンプレートを返すことができます。
ちなみに後の章で紹介しますが、API の URL はどうなるかというと、この記述より上に定義します。Laravel のルーティングは記述順にリクエストにマッチするか探索されるためです。
テンプレート
テンプレートは以下の内容で resources/views/index.blade.php
を作成してください。
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name') }}</title>
<!-- Scripts -->
<script src="{{ mix('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Merriweather|Roboto:400">
<link rel="stylesheet" href="https://unpkg.com/ionicons@4.2.2/dist/css/ionicons.min.css">
<!-- Styles -->
<link rel="stylesheet" href="https://hypertext-candy.s3-ap-northeast-1.amazonaws.com/posts/vue-laravel-tutorial/app.css">
</head>
<body>
<div id="app"></div>
</body>
</html>
コンテンツは空の <div id="app"></div>
だけですね。ここに Vue アプリが描画されます。
また、元ある welcome.blade.php
は使用しないので削除してください。
JavaScript
次に resources/js/app.js
を編集します。
import Vue from 'vue'
new Vue({
el: '#app',
template: '<h1>Hello world</h1>'
})
まず先に記述したルーティングの働きを確かめるために、Hello world
とだけ表示させます。
フロントエンドのビルド
ビルドコマンドは npm スクリプトにまとめられているので、以下のコマンドで監視モードのコンパイルが走ります。つまり一度コンパイルが走った後に監視モードに入り、ファイルの変更があるたびに自動的に再度コンパイルが実行されます。
$ npm run watch
ちなみに、上のコマンドを初回実行したとき、コンソールに以下のメッセージが出力されます。これは Laravel Mix が足りない npm パッケージを自動でインストールするよ、と言っています。エラーなどではないのでご安心ください。少し待てば終わるので、もう一度 watch
コマンドを実行しましょう。
Additional dependencies must be installed. This will only take a moment.
Running: npm install vue-template-compiler --save-dev --production=false
Okay, done. The following packages have been installed and saved to your package.json dependencies list:
- vue-template-compiler
Additional dependencies must be installed. This will only take a moment.
Running: npm install browser-sync browser-sync-webpack-plugin@2.0.1 --save-dev --production=false
Okay, done. The following packages have been installed and saved to your package.json dependencies list:
- browser-sync
- browser-sync-webpack-plugin@2.0.1
Finished. Please run Mix again.
BrowserSync によって自動的にブラウザに http://localhost:3000 が開かれます。これは自動リロードが有効になったアドレスです。以降はこちらの URL でアクセスしましょう(http://vuesplash.test もアクセスできますが自動リロードがかかりません。)。
ここまでで一度ブラウザで動きを確認しましょう。
/
にアクセスして「Hello world」が表示されているか確かめてください。- さらにアドレスバーから任意のパスにアクセスしてみて、必ず index テンプレートが返却されている(つまり「Hello world」が表示されている)ことを確認してください。
Vue Router
この章の最後に、Vue Router を導入します。
インストール
まずは npm でインストールします。
$ npm install -D vue-router
ルートコンポーネント
ここから Vue コンポーネントをいくつか作成します。
まずはルートコンポーネント resources/js/App.vue
を作成します。
<template>
<div>
<main>
<div class="container">
<RouterView />
</div>
</main>
</div>
</template>
ルートコンポーネントとは、コンポーネントツリーの頂上に位置するコンポーネントです。<div id="app"></div>
にこのコンポーネントが描画されます。その他のコンポーネントは直接または間接的にこのコンポーネントから呼び出されることになります。
<RouterView />
に注目してください。このコンポーネントは Vue Router によって提供されています。前章の SPA での「画面遷移」 についての説明を思い出して(もしくは読み返して)ください。「URL によって HTML 部品が切り替わる」と説明しましたが、<RouterView />
が切り替わる箇所を定義します。紙芝居の枠のようなものと考えればよいでしょう。つまり URL が変わるたびに、<RouterView />
の部分に、URL に対応する HTML 部品が入れ替わって描画されます。
ページコンポーネント
さて、次に「切り替わる HTML 部品」を作成します。Vue アプリでは HTML 部品はもちろん Vue コンポーネントとして表現されます。
まず resources/js
ディレクトリの下に新たに pages
ディレクトリを作成します。ここをページコンポーネントの置き場所とします。
それから pages
の中に以下のふたつのコンポーネントを作成してください。
写真一覧ページを表す PhotoList.vue
コンポーネントです。
<template>
<h1>Photo List</h1>
</template>
ログインページを表す Login.vue
コンポーネントです。
<template>
<h1>Login</h1>
</template>
まだ有意義な内容ではありませんが、中身は後々の章で作っていきます。今のところは Vue Router による画面の切り替わりが確認できれば十分です。
ルーティング
続いてルーティング定義を作成します。
以下の内容で resources/js/router.js
を作成してください。
import Vue from 'vue'
import VueRouter from 'vue-router'
// ページコンポーネントをインポートする
import PhotoList from './pages/PhotoList.vue'
import Login from './pages/Login.vue'
// VueRouterプラグインを使用する
// これによって<RouterView />コンポーネントなどを使うことができる
Vue.use(VueRouter)
// パスとコンポーネントのマッピング
const routes = [
{
path: '/',
component: PhotoList
},
{
path: '/login',
component: Login
}
]
// VueRouterインスタンスを作成する
const router = new VueRouter({
routes
})
// VueRouterインスタンスをエクスポートする
// app.jsでインポートするため
export default router
記述内容の意味はコメントの通りです。初見でも結構わかりやすいかと思います。
最後に app.js
を編集します。
import Vue from 'vue'
// ルーティングの定義をインポートする
import router from './router'
// ルートコンポーネントをインポートする
import App from './App.vue'
new Vue({
el: '#app',
router, // ルーティングの定義を読み込む
components: { App }, // ルートコンポーネントの使用を宣言する
template: '<App />' // ルートコンポーネントを描画する
})
ここでブラウザで動作を確認してみましょう。npm run watch
コマンドが実行されていればコンパイルは自動的に済んでいるはずなので、ブラウザを再読み込みしてください。
/
にアクセスしてください。URL が/#/
に変わって「Photo List」と表示されるはずです。- 次に
/#/login
にアクセスします。「Login」と表示されるはずです。
history モード
ここまでで Vue Router を使ったフロントエンドの画面遷移が実装できました。しかしお気づきの通り、URL にハッシュ #
がついてしまっています。Vue Router のデフォルトの挙動は、URL 文字列中のハッシュの変化では画面遷移が発生しないブラウザの仕様を利用しているためです。
本来の URL の形を再現するには、History モードを用います。
router.js
で VueRouter インスタンスを生成する際のオプションに以下を追記してください。
const router = new VueRouter({
mode: 'history', // ★ 追加
routes
})
ブラウザを再読み込みして動作を確認しましょう。
/
にアクセスしてください。URL はそのままで「Photo List」と表示されるはずです。- 次に
/login
にアクセスします。「Login」と表示されるはずです。
ちなみにこの段階で当てずっぽうなパスにアクセスすると何も表示されません。index テンプレートは返却されていますが対応するコンポーネントの定義がないため何も表示できないのです。
第3章はこれでおしまいです。
ここまでのソースコードはリポジトリの ch-3 ブランチに置いてあります。
Vue Router で画面を切り替えられる仕組みが手に入ったので、次の章から認証機能の実装に取り組みます。
関連記事
連載記事(全16回)
Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう
- (1) イントロダクション
- (2) アプリケーションの設計
- (3) SPA開発環境とVue Router
- (4) 認証API
- (5) 認証ページ
- (6) 認証機能とVuex
- (7) 認証機能とVuex Part.2
- (8) エラーハンドリング
- (9) 写真投稿API
- (10) 写真投稿フォーム
- (11) 写真一覧取得API
- (12) 写真一覧ページ
- (13) 写真詳細ページ
- (14) コメント投稿機能
- (15) いいね機能
- (16) エラーハンドリング Part.2