Vue と Nuxt と Firebase でサクッとアプリを作る

Vue.js での開発はいくつか経験したことがあるが、Nuxt.js は触ったことがなかったので入門してみた。
SSR やユニバーサルといったところは置いておいて、ひとまず簡単な SPA を作ってみてメモや雑感をまとめました。

以下は `nuxt@v1.0.0-rc11` での内容になります。

# 作ったもの

[Civirization ボードゲーム](https://hobbyjapan.co.jp/civ/)の技術ツリーを管理・閲覧するためのツール。
もともと２年ほど前に Angular + Firebase で作っていたものをベースに、Vue.js + Nuxt.js + VuexFire で書き直したものです。

ゲームの内容はさておき、各自が手元のスマホで操作するとリアルタイムに技術ツリーが同期・共有される。
認証やバリデーションなど細かいところは詰めてないですが、身内で使うには十分かなと。

[ソースコードはこちら](https://github.com/wiro34/civbg-support)

# Nuxt.js とは

詳しくは[公式サイト](https://ja.nuxtjs.org/guide)に任せるとして、ざっくり説明すると

* Vue.js 製のフロントエンドフレームワーク
* SSR対応
* ルーティングやストアの自動生成などを行う
* ES6/ES7 のトランスパイル
* SASS, LESS, Stylus などの CSS プリプロセッサのサポート

などがあります。

Nuxt.js はソースコードのバンドルに Webpack を利用しており、CSS プリプロセッサや babel-loader を内包しています。
これらの設定は Nuxt.js が隠蔽しており簡単に使い始めることができます。
（適宜設定を追加することも可能です。）

# Firebase と VuexFire

[Firebase](https://firebase.google.com/) は BaaS / mBaaS と呼ばれるアレ。
なにやらたくさん機能があるけど、今回使ったのはリアルタイムデータベース (RTDB) のみ。
誰かがデータを更新すると自動で他のユーザにも更新が反映される。

[VuexFire](https://github.com/posva/vuexfire) は Firebase の Vuex バインディング。
データベースが更新されると、その内容を自動で Vuex のストアに反映してくれる。
※双方向バインディングではない点に注意。詳細は後述。

# 画面遷移

今回は以下の２ページ構成にしています。

* Index (トップページ: 開始ボタンがあるだけ)
* Game (ゲーム画面: 技術ツリーを表示)

Game 画面はクエリパラメータから `gameKey` を受け取ります。
pages ディレクトリは以下のとおり。

```
/ROOT/
  └ /pages/
      ├ index.vue
      └ game.vue
```

Nuxt.js では、[ファイルの配置によって自動的にルーティングが生成](https://ja.nuxtjs.org/guide/routing)されます。

今回は Github Pages 上で公開する予定だったので、静的なルーティングのみ利用しています。

# Vuex と VuexFire の設定

[ドキュメント](https://ja.nuxtjs.org/guide/vuex-store)に従い、Vuex ストアをモジュールモードで設定します。
`store/index.js` を作成し、`store`, `mutations`, `actions` を export するだけで Vuex ストアが自動生成されます。

あわせて VuexFire の設定も行います。
mutations に `firebaseMutations` を含め、`firebaseAction` の `bindFirebaseRef` でバインドする state のキーと Reference
 を設定します。

ソースコードの抜粋でわかりにくいですが、`LOAD_GAME` アクションを実行すると RTDB の `games/<gameKey>/players` が `state.players` にバインディングされるようになっています。

```javascript:store/index.js
import { firebaseMutations, firebaseAction } from 'vuexfire'
import db from '~/plugins/firebase'
import { SET_GAME_ID } from './mutation-types'
import { LOAD_GAME, SET_PLAYERS_REF } from './action-types'

const gamesRef = db.ref('games')

export const state = () => ({
  gameId: null,
  players: [],
})

export const mutations = {
  ...firebaseMutations
}

export const actions = {
  async [LOAD_GAME] ({commit, dispatch}, gameKey) {
    const gameRef = gamesRef.child(gameKey)
    const gameSnapshot = await gameRef.once('value')
    const game = gameSnapshot.val()
    if (game && ('players' in game)) {
      commit(SET_GAME_ID, gameRef.key)
      dispatch(SET_PLAYERS_REF, gameRef.child('players'))
      return true
    }
    return false
  },

  [SET_PLAYERS_REF]: firebaseAction(({bindFirebaseRef, commit}, playersRef) => {
    bindFirebaseRef('players', playersRef)
  }),
}
```

```javascript:plugins/firebase.js
import firebase from 'firebase'

const config = {
  databaseURL: 'https://civbg-support.firebaseio.com/'
}

if (firebase.apps.length === 0) {
  firebase.initializeApp(config)
}

export default firebase.database()
```

## データの更新

VuexFire は Firebase → Vuex への片方向バインディングのみを行います。
そのためデータの更新は state の更新ではなく、Firebase API を直接叩いて行う必要があります。

おそらくトランザクション制御や一括書き込みなどを Vuex で表現できないので、あえて片方向にしているんだと思います。
＃最初、双方向バインディングだと思い込んでいてハマりました…。

```javascript:store/index.js
export const actions = {
  ...

  async [ADD_TECH] ({state, commit}, {player, level, techId}) {
    const playersRef = gamesRef.child(state.gameId).child('players')
    await playersRef.child(player['.key']).transaction((p) => {
      if (p) {
        p.tree = {first: [], second: [], third: [], fourth: [], ...p.tree}
        p.tree[level].push(techId)
      }
      return p
    })
  },

  ...
}
```

Firebase は要素が null や [] の場合キーごと消えてしまうので都度初期化しています。

VuexFire を利用する場合のデータフローは以下の記事が図解されておりわかりやすかったです。

* [Vue.js+Vuex+VuexFireでTodoList](https://qiita.com/frost_star/items/eea52cb33a749272d948) 

# 雑感

ざっと触った感じでは、開発の立ち上がりは非常に楽でした。
Webpack のビルド設定まわりはいつも面倒に感じていたので、省略できるのはありがたいです。

ただ、まだ発展途上を思わせるところもあった。

デバッグのために一時的に Minify (Uglify) をオフにしたかったのだが、[client.config.js](https://github.com/nuxt/nuxt.js/blob/v1.0.0-rc11/lib/builder/webpack/client.config.js#L199-L211) を見てみると、そもそもオフにする設定が `v1.0.0-rc11` にはなかった。
[dev ブランチの client.config.js](https://github.com/nuxt/nuxt.js/blob/dev/lib/builder/webpack/client.config.js#L155-L173) には設定が追加されていたので、近いうち利用できるようになるかも。

追記：下書きしている途中に Nuxt.js `v1.0.0` が正式リリースされたようです。上記の内容も取り込まれていました！

正式リリースされたようなので、次は業務で使ってみようかな。