Laravelばかりやってた人間がVue.jsでSPAを作るまでの学習記録

..

現状の成果物

Laravel+Vueのリポジトリ: https://github.com/nawawa/spa-tutorial-laravel
Vue単体SPAのリポジトリ: https://github.com/nawawa/spa-tutorial-vue

筆者の背景

7ヶ月間のエンジニアインターンでは、ほとんどの間Laravelによるバックエンドの実装をさせていただきました。

フロントエンドも多少触らせていただきましたが、jQueryをメインに使用するコンポーネント化を一切行わない中での業務となっていたため少し面倒な部分も多く、学習しやすいといわれるVue.jsにずっと興味を持っていました。

業務を行うに当たっての補助的な学習に追われていましたが、インターン期間が終了したことで時間を確保できたこともあり、本格的に学習してみました。

記事の趣旨

Vue.jsをゼロから学習し、いわゆるSPAでWebアプリケーションを作成するまでの学習記録です。

SPAとは?

一枚のHTMLページの中で、JavaScriptから生成したHTMLを入れ替えることで擬似的にページ遷移を行うアーキテクチャです。アクセスした段階でHTMLを生成するので初期表示が遅くなるほか、DOMをGoogleのクローラーが解析できないのでSEOに弱いとも言われています。

ただデプロイが容易であることと、一回描画が済んでしまえば全体の読み込みが発生しないため、かなり高速に動作させられるのがメリットです。

学習その1:CDN+書籍

こちらのサイトに掲載されているチュートリアルを見ながら、環境構築はしないままハンズオン的に触ってみました。LaravelのBladeテンプレートに慣れていたので、Vue.js自体の基本的なディレクティブ等の機能は抵抗なく理解することができました。

上記サイトは『基礎から学ぶ Vue.js』という書籍のサポートサイトとなっており、最初のチュートリアルを終えた後は実際に書籍にて学習を継続しています。

学習その2:Laravelと組み合わせたチュートリアル

書籍での学習に飽きてきたこともあり、実際に動くものを作ってみようということで、@minato-naka様のこちらのチュートリアルにて開発を体験しました。

最初からバックエンドから切り離された形で作るのはハードルが高く感じられたので、LaravelのBladeテンプレートと組み合わせてSPA化するこちらの内容はとてもちょうどよかったように思います。

実務ではjQueryとES6構文で実装したソースをLaravel-mixでトランスパイルしていたこともあり、いい比較になりました。

学習その3:バックエンドを流用し、別で単独SPAを制作

Laravelで実装したAPIをそのまま使い、フロントエンド部分だけを切り離して独立したVueアプリケーションを作りました。

以下、実践した内容です。

vue-cli+docker-composeを使った環境構築

いちど使い始めてからはずっとコンテナ至上主義です…tet0h様による記事(Zenn)に従ってローカルを構築しました。ただDockerfileだけこのようにすることで、npmのアップデートを行う際のバグを回避する必要があるようです。

FROM node:15.11.0-alpine

WORKDIR /app

RUN apk update && \\
    npm install && \\
    npm install -g agentkeepalive --save \\
    npm install -g npm@latest && \\
    npm install -g @vue/cli

学習メモ:vue-cliは、Vue.js公式の開発支援ツールです。自前で一から用意するとめんどくさいような色々を揃えてくれます。

  • ESLint(構文チェッカー)の導入
  • 単一ファイルコンポーネントをビルドする機能
  • テストツール(Jestとか)の導入
  • トランスパイラの導入…など

バージョンを指定していないので、この場合は最新版(2021年9月現在は4系)がインストールされます。

Vueアプリケーション立ち上げ

ビルドしたコンテナに入り、vue init webpack アプリ名を実行することでVue2系アプリケーションの雛形が作られます。

コンテナ内でバージョン確認

/app # npm list -g vue
/usr/local/lib
`-- @vue/cli@4.5.13
  `-- vue@2.6.14

学習メモ:新しいアプリケーション作成時のコマンドにはvue createvue initがありますが、vue-cliのバージョン2系まではinit、3系以降はcreateが使えるようになったそうです。(@teraco様の記事より)
ディレクトリ構成に違いがあるもののどちらかでしかできないことはなく、筆者はこれを知らないまま記事をなぞってvue init webpackで作りました。公式としてはvue createが推奨のようで、実際vue initで作成する場合と比較して、細部が効率化できるとのことです。

Vue.jsによるSPAの構造

このようにして作られたVueアプリケーションは、以下のようなディレクトリ構成となっています。

./
├─build/
│    ├─build.js
│    ├─check-versions.js
│    ├─logo.png
│    ├─utils.js
│    ├─vue-loader.conf.js
│    ├─webpack.base.conf.js
│    ├─webpack.dev.conf.js
│    ├─webpack.prod.conf.js
│    └─webpack.test.conf.js //※2
│
├─config/
│    ├─dev.env.js
│    ├─index.js
│    ├─prod.env.js
│    └─test.env.js
│
├─src/
│    ├─assets/
│    └─logo.png
│    ├─components/
│    └─HelloWorld.Vue
│    ├─router/
│    ├─App.vue
│    └─main.js
│
├─static/
│    └─.gitkeep
│
├─test/ //※2
│    ├─e2e/
│    └─unit/
│
├─index.html
├─package.json
├─README.md
├─.editorconfig
├─.eslintignore //※1
├─.eslintrc.js //※1
├─.gitignore
└─.postcssrc.js

これらファイルからどのようにアプリが形成されるかというと…
一部のファイルに絞って見てみます。

./
├─src/
│    ├─assets/
│    ├─components/
│    └─HelloWorld.Vue
│    ├─router/
│    ├─App.vue
│    └─main.js
├─index.html

npm run devでアプリをビルドする際index.htmlが参照され、SPAの基盤となるHTMLが生成されます。
なので一度Ctrl Cでストップしたあと<title>を書き換え、もう一度ビルドすると、ちゃんとブラウザタブに表示されているタイトルが変わっていることがわかります。

では、ページの中身はどのように定義されているのか。
index.jsを見てみると、このようなソースがあります。

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

Vueインスタンスのローカルに、Appコンポーネントが登録されています。
つまり「アプリ全体からAppコンポーネントへのアクセスができる」ということで、今のところはAppコンポーネントがアプリの全体像そのものとなっています。

Appコンポーネントはこのようになっています。

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

一番下にはCSS、その上のscriptはAppという名前でエクスポートしているだけ。
一番上のテンプレートでは<router-view/>というタグがあります。これがつまり、このSPAの切り替わる部分であり、各ルートごとのコンポーネントが表示される部分ということになります。

ルートはindex.jsに記載されています。

export default new Router({
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})

定義されているのはトップページのルートのみ。
トップページにアクセスするとHelloWorldコンポーネントのテンプレートが描画されるようになっています。他のページを追加する際は、HelloWorldコンポーネントの位置に違うコンポーネントを指定することで遷移をさせることになります。

チュートリアルで作ったLaravelフロントエンドを完全SPA化

以上をおさえつつ、全く同じ構成のSPAを構築していきました。

やったことは以下の通りです。

トップページ用コンポーネント追加
前回のチュートリアルでは特にトップページを作り込むようなことはありませんでしたが、どうせならと空のコンポーネントを追加。

bootstrap-vueの追加
先のチュートリアルに合わせ、スタイルはBootstrapを使用。
参考記事:Vue.jsにBootstrapを適用させる方法

axiosの追加
環境構築ではインストールしなかったので、後から追加しています。
参考記事:vue-cliでaxiosを使用する(設定から使用方法まで)

ルートからハッシュ記号を除くため、routerの設定を変更
参考記事:Vue.jsでURLの#(シャープ)を取り除く方法

以上により、まったく同じSPAを作ることができました。

スクリーンショット 2021-09-16 13.50.08.jpeg
スクリーンショット 2021-09-16 13.50.01.jpeg

今後の学習について

一からVue.jsを勉強し始めて、単独のSPAを作るまでの学習内容をまとめました。
今後は取得したデータを永続化する等の目的でVuexを使ってみたり、BootstrapではなくVuetifyでスタイル付与・レスポンシブ対応をしてみたり、その後はNuxt.jsでSSRやSSGなどもできるようになりたいと考えています。

最終的にはRails+NuxtでSSRするWebアプリを作ることをいったんの目標としていますが、どの程度の時間がかかるかわかりません。急がず進めていこうと思います。

その他参考記事

Vue-cli(webpack)解剖 ーディレクトリ構成ー
Vue.js を vue-cli を使ってシンプルにはじめてみる