豆乳とコストコのマフィンが好きなtomita@atuwebです。

この数年、たいへんなスピードでフロントエンド開発技術が発展しておりますね。
私はバックエンド開発が続いていたことを言い訳に、直近の小規模なWebツール開発をjQueryで頑張ってしまったため、次は同じ轍を踏まない ためにVue.jsを勉強してみました。

jQueryで頑張ってはいけない理由

jQuery(ジェイクエリー)は、ウェブブラウザ用のJavaScriptコードをより容易に記述できるようにするために設計された軽量なJavaScriptライブラリ

https://ja.wikipedia.org/wiki/JQuery

jQueryはフロントエンドの開発に大きく貢献してきた素晴らしいライブラリで、「Webサイトにちょっとしたギミックを足す」用途には使いやすいツールです。

しかしながら、jQueryでの開発で、少しでも規模が大きくなってくると途端に苦しくなってきます。

それは「DOMの操作」や「データの連携」などはすべて自前で実装する必要があるためで、作り込めば作り込むほど、「 変更が聞かないガチガチのプログラム 」になってしまいます。


私がjQueryを初めて利用したのは2011年に開発したブラウザゲームでした。
その頃はまだこれほどまでにフロントエンドの開発技術が発展する前で、JavaScriptのフレームワークも限られたものしかありませんでした。

最初はギミックが小さかったため良かったのですが、プロジェクトが世に出てフロントエンドの要件が増えれば増えるほど、JSソースが混沌とし、ダークサイドへと堕ちていくよが自分でもよくわかりました。

オライリーのJavaScript本をこ購入してコードの改善を図りましたが、限界というものはあるものです。 悔しいかったですね。

このプロジェクトは、その後半年ほどで私の手を離れました。

次にこのプロジェクトを引き継いだエンジニアさんに、「黒いコードを引き渡してごめんなさい」と激しくお詫びしたかったです。
ごめんなさい。


そして2014年に、札幌の有名企業インフィニットループさんが外部解放されている勉強会に参加し、その中で次のお話を伺い「 JSにもこんな素晴らしいライブラリがでてきたんだな 」と時の経過を感じたものです。

株式会社インフィニットループ技術ブログ
5分でわかるVue.jsと、jQueryで頑張ってはいけない理由
https://www.infiniteloop.co.jp/blog/2014/06/5min_vuejs/

さて、前置きが長くなりました。

ゴール

Vue.jsを使ってバックエンドと通信する簡単なWebアプリを実装する。

  • Laravel 5.4を導入しLaravel Mixを利用する
  • データ管理はバックエンド
  • フロントエンドはvue.js

Laravel Mixとは

Laravel Mixとは「LaravelでCSSやJavaScriptをビルドするためのAPIを提供するもの」です。

Laravel Mix
https://laravel.com/docs/5.4/mix

Laravel 5.3まではGulpを利用しLaravel Elixirという名称でした。

Laravel 5.4からWebpackの利用に変更され、「異なるライブラリがベースである」ことがわかるように名称も変えたという理解です。

Vue.jsとは

Vue.jsとは データバインディングに特化 したシンプルで軽量なJSフレームワークです。

Vue.js
https://jp.vuejs.org/

ドキュメントが豊富であり、かつ学習コストも高くありません。

そして、最初からLaravel Mixに組み込まれています。

環境

  • macOS Serrie
  • Laravel 5.4
  • PHP 7.0
  • node.js 7.7.1
  • npm 4.4.2

PHPは次で環境構築済みです。

[Mac]phpbrewを導入してみた
atuweb.net

node.jsは今回新しく環境を整えます。

実装編

Laravelプロジェクトを作成し、Mixを準備

プロジェクトの作成

Laravel5.4の新規プロジェクトを追加します。

$ composer create-project laravel/laravel MyProject 5.4
$ cd MyProject
$ composer install

node.jsの導入

Laravel Mixを利用するためにはNode.jsの環境構築が必須です。

homebrewでのインストールではなく、便利そうなnodebrewを導入しました。

インストール
$ brew install nodebrew
nodebrewのパスを通す
$ vi ~/.bash_profile
export PATH=$HOME/.nodebrew/current/bin:$PATH
$ source ~/.bash_profile
利用可能バージョン確認
$ nodebrew ls-remote
インストールと利用バージョン指定
$ nodebrew install-binary latest
$ nodebrew use latest
インストール確認
$ node -v
v7.7.1
$ npm -v
3.10.10
npmをアップグレードする
$ npm install -g npm
$ npm -v
4.4.2

Laravel Mixの準備

Laavel Mixの依存ライブラリはプロジェクトルートのpackage.jsonに定義されています。

私はnpm runでエラーが発生したため、package.jsonを次のように編集しました。 詳しくは後述しております。

{
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "watch-poll": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
    "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
    "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
  },
  "devDependencies": {
    "axios": "^0.15.3",
    "bootstrap-sass": "^3.3.7",
    "cross-env": "^3.2.3",
    "jquery": "^3.1.1",
    "laravel-mix": "^0.8.1",
    "lodash": "^4.17.4",
    "vue": "^2.1.10"
  }
}

npmコマンドを実行しLaravel Mixをインストールします。

$ cd MyProject
$ npm install

その後、プロジェクトルートでrunすると、JSやCSSファイルがビルドされます。

$ npm run dev

ビルドが成功すると上のポップアップが表示されます。 嬉しくなる瞬間ですね。

ゴリゴリコードを書く段階では、ファイルの変更を監視し、速やかに処理してくれるrun watchがおすすめです。


「何をどこに配置するか」はwebpack.mix.jsに定義されています。

const { mix } = require('laravel-mix');

mix.js('resources/assets/js/app.js', 'public/js')
   .sass('resources/assets/sass/app.scss', 'public/css');

フロントエンドの開発

Qiitaさんに、自分にちょうど良い記事があり、追って開発することでVue.js開発の理解を得ました。 次の記事を基本に、ちょっと手を加えたものを掲載していきます。

LaravelからVueを使ってSPAっぽくする
http://qiita.com/acro5piano/items/712b6b84151b53d0c53b

Vue.jsの実装の前に、Laravelが担当する処理を実装します。

app.blade.php

JavaScriptライブラリで構築したWebアプリケーションであっても、「 まずはHTMLを出力し、そのHTMLからjsコードを実行する 」という流れが基本です。

このHTMLをBladeで実装します。

resouces/views/app.blade.php
<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
    <div id="app">
        <navbar></navbar>
        <div class="container">
            <router-view></router-view>
        </div>
    </div>
</body>
<script src="{{ mix('js/app.js') }}"></script>
</html>

<router-view>タグは、router-viewによって動的に各ページのコンテンツに差し替えられます。

サーバサイドルーティング

上記のHTMLを出力する「TOPページ」と「Vue.jsからアクセスするAPI」のURLをLaravelに定義します。

routes/web.php
Route::get('/', function() {
    return view('app');
});
routes/api.php
Route::group(['middleware' => 'api'], function() {
    Route::get('book',  function() {
        return [
            ['id' => 1, 'title' => 'リーダブルコード'],
            ['id' => 2, 'title' => 'プリンシプル オブ プログラミング'],
            ['id' => 3, 'title' => 'リファクタリング(新装版)'],
            ['id' => 4, 'title' => '情熱プログラマー'],
        ];
    });
});

今回は仮実装のため、ルーターから直にレスポンスを返しています。

クライアントサイドルーティング

app.jsを修正してクライアントサイドのルーティングを定義します。
取り急ぎ、TOPページに対応するvueのみとします。

resources/assets/js/app.js
import Vue from 'vue'
import VueRouter from 'vue-router'

require('./bootstrap')

Vue.use(VueRouter)

const router = new VueRouter({
    mode: 'history',
    routes: [
        { path: '/', component: require('./components/Articles/Index.vue') }
    ]
})

const app = new Vue({
    router
}).$mount('#app')

ajaxアクセス

クライアントサイドからのHTTPリクエストにはpackage.jsonに定義済みのaxiosを使います。

bootstrap.jsを修正し、Vue.jsのコード内からaxiosを利用できるようにしましょう。

resources/assets/js/bootstrap.js
window.axios = require('axios')

window.axios.defaults.headers.common = {
    'X-Requested-With': 'XMLHttpRequest'
}
Vue.prototype.$http = axios

TOP画面のリストを実装する

先ほどクライアントサイドルーティングで定義したIndex.vueを実装します。

resouces/assets/js/components/Articles/Index.vue
<template>
    <div>
        <div v-for="article in articles">
            <div class="row">
                <div class="thumbs">
                    <router-link :to="'/book/' + article.id"><img :src="'/img/books/' + article.id + '.jpg'" :alt="article.title"></router-link>
                </div>
                <div class="book-data">
                    <router-link :to="'/book/' + article.id">{{ article.title }}</router-link>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        created() {
            this.fetchArticles()
        },
        data() {
            return {
                articles: []
            }
        },
        methods: {
            fetchArticles() {
                this.$http({
                    url: '/api/book',
                    method: 'GET'
                }).then(res =>  {
                    this.articles =  res.data
                })
            }
        }
    }
</script>

ローカルホストを立ち上げ、ブラウザでアクセスすると次の画面が表示されました!

画像ファイルは[ public/img/books ]以下に直接を設置しております。

トラブルシュート

cross-env.jsの追加

npm runで次のようなエラーが発生しました。

$ npm run dev

> @ dev /Path/to/Project
> node_modules/cross-env/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

sh: node_modules/cross-env/bin/cross-env.js: No such file or directory
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn
npm ERR! @ dev: `node_modules/cross-env/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the @ dev script 'node_modules/cross-env/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the  package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node_modules/cross-env/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls
npm ERR! There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/[user_name]/.npm/_logs/2017-03-10T05_07_17_593Z-debug.log

次のissueを参考に、package.jsonを前述のものに修正しました。

https://github.com/JeffreyWay/laravel-mix/issues/478

コンパイルエラー

はじめに導入したNode.jsのバージョンは6系にしていました。
上記のエラーでNode.jsのバージョンも関係しているのかと思い、Node.jsのバージョンを変更したところ、次のエラーが発生するようになりました。

Module build failed: Error: Missing binding Path/to/Project/node_modules/node-sass/vendor/darwin-x64-51/binding.node
Node Sass could not find a binding for your current environment: OS X 64-bit with Node.js 7.x

Found bindings for the following environments:
 - OS X 64-bit with Node.js 6.x

npmのキャッシュクリア、sassのリビルドを行なったところ、このエラーは解消しました。

$ npm cache clean
$ npm rebuild node-sass
$ npm install --save-dev cross-env
$ npm install

リビルドは次のissue参考にいたしました。

https://github.com/sass/node-sass/issues/1585

おわりに

今回はさらっとしかVeu.jsを触っていませんが、そのメリットは十分に理解できました。
1サイトとして作り込んで行く予定です。

また、最新のWEB+DB PRESSにSPAの特集が掲載されております。
フロントエンド開発のメリット、デメリット、対処法が的確にまとめられており、最近の動向を把握できるいい内容でしたよ。


参考サイト

Node.jsの管理をHomebrewからnodebrewに変える
http://qiita.com/takeshi81/items/805f504503cd93151ca6

LaravelからVueを使ってSPAっぽくする
http://qiita.com/acro5piano/items/712b6b84151b53d0c53b

株式会社インフィニットループ技術ブログ
5分でわかるVue.jsと、jQueryで頑張ってはいけない理由
https://www.infiniteloop.co.jp/blog/2014/06/5min_vuejs/

ある蜜柑の上にアルミ缶
まだjQueryで消耗してるの? これからはVue.jsでラクにいこう
https://s8a.jp/jquery-to-vuejs


2017年09月23日:リンクが表示されていなかった不具合を修正
2017年03月25日:アイキャッチ画像を変更