Nuxt+Vuetify+FirebaseでWEBアプリを開発する

## はじめに
「WEBアプリをサクッと開発してサクッと公開したい」と思い、実際にやってみました。
その備忘録です。

## 更新情報
- 2019/4/27 ログインの実装まで
- 2019/5/24 ミドルウェアの実装まで

## Vue.jsとは
簡単に言うと「JavaScriptのフレームワークのひとつ」です。
ReactやAngularと同列のやつですね。
（正確にはReactはライブラリらしいですが）

https://jp.vuejs.org/index.html

## Nuxtとは
Vue.jsのフレームワークです。
vue-routerやvuexなどが最初から入ってます。
開発をviewだけに集中することができるらしいです。
（Nuxt.jsという名前はReactのフレームワークであるNext.jsから来てるらしい（パクリｗ）

https://ja.nuxtjs.org/

## Vuexとは
Vue.jsアプリケーションのためのVuexとは状態管理のためのライブラリです。
コンポーネント間での値の受け渡しがわかりやすくなります。

https://vuex.vuejs.org/ja/

## Vuetifyとは
VuetifyとはVue.jsで使えるマテリアルデザインのフレームワークです。
デザインフレームワークはいろいろありますが、個人的にこれがビビッときました。

https://vuetifyjs.com/ja/

## Firebaseとは
Googleが提供しているmBaaSです。
バックエンドをまるっと請け負ってくれます。
かつ無料である程度のことができます。

https://firebase.google.com/?hl=ja

### 今回使うFirebaseの機能
#### Authentication
メールアドレスとパスワードで認証を管理します

#### Firestore
DBとして使います

#### Storage
画像を保存します

#### Hosting
デプロイします



## 環境
- macOS Mojave 10.14.2
- git 2.17.2
- yarn v1.13.0
- node v10.15.0
- npm 6.4.1
- npx 6.4.1
- firebase-tools 6.7.0
- Nuxt.js 2.4


## プロジェクトの作成

```
npx create-nuxt-app project_name(プロジェクト名は適宜）
```

npxはnpmのバージョンが5.2.0以上じゃないと使いないそうです。

https://github.com/nuxt/create-nuxt-app

### 質問
1. プロジェクト名は？ → デフォ
2. プロジェクト概要は？ → デフォ
3. サーバのフレームワークは？ → none
4. axiosなどをインストールしますか？ → しない（必要であれば後で入れる）
5. UIフレームワークは？ → vuetify
6. テストフレームワークは？ → none（必要であれば後で入れる）
7. レンダリングモードは？ → SPA
8. 名前は？ → デフォ
9. パッケージマネージャーは？ → yarn

完了！


```
yarn dev
```

ローカルで動作確認する。（http://localhost:3000/ ）

めっちゃリッチなサンプルページが表示されたｗｗ

### 参考
https://ja.nuxtjs.org/guide/installation/


## git管理下にする

```
cd project_name
git init
git remote add origin https://github.com/user_name/project_name.git
git add README.md
git commit -m 'first commit'
git add .
git commit -m 'create-nuxt-app'
git push origin master
```

完了！

## Firebaseのプロジェクト作成
https://firebase.google.com/?hl=ja

ここでプロジェクトを作成する。

## Firebaseとプロジェクトを紐付ける

firebase-toolsをインストールする

```
yarn global add firebase-tools
```

npmの人はこっち

```
npm i -g firebase-tools
```

yarnのグローバルインストールモジュール確認（firebase-toolsが入ったか確認するため）

```
yarn global list --depth=0
```

firebase-toolsのバージョン確認

```
firebase -V or firebase --version (firebase -v は出来ない...）
```

6.7.0が最新だった

firebaseにログインする

```
firebase login
```

プロジェクトにfirebaseを導入する

```
firebase init
```

### 質問
1. 使う機能は？ → 一旦hostingのみ
2. Firebaseプロジェクトは？ → （上で作成したプロジェクト)
3. 公開ディレクトリは？ → dist（Nuxt.jsがデフォルトでdistなので）
4. Configure as a single-page app? → どっちでもいい

.firebasercとfirebase.jsonファイルが生成される


### firebaseにデプロイする
nuxtの静的ページを作成する

```
yarn generate
```

`dist/`ができるはず

deployする

```
firebase deploy
```

URL(https://project_name.firebaseapp.com/ )が発行されて、デプロイ完了！


ここまで、プロジェクトの作成から公開までやってきました。

### 参考
https://omiend.hatenablog.jp/entry/2018/12/01/121105


## FirebaseとNuxtを紐付ける

firebaseライブラリをyarnでローカルに入れる

```
yarn add firebase
```

(.firebaseはgitignoreした）

pluginsディレクトリ下にfirebase.jsというファイルを作成する

```
touch plugins/firebase.js
```

```firebase.js
import firebase from 'firebase'

if (!firebase.apps.length) {
  firebase.initializeApp({
    apiKey: process.env.FIREBASE_API_KEY,
    authDomain: process.env.FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.FIREBASE_DATABASE_URL,
    projectId: process.env.FIREBASE_PROJECT_ID,
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID
  })
}

export default firebase
```

中身はこんな感じ。

SPAが呼び出された時点でfirebaseを読み込みたいので、nuxt.config.jsのpluginsに追記

```nuxt.config.js
plugins: [
  '@/plugins/firebase.js'
]
```

環境変数をプロジェクトから切り離すため、.env.templateを作成する

```..env.template
FIREBASE_API_KEY=
FIREBASE_AUTH_DOMAIN=
FIREBASE_DATABASE_URL=
FIREBASE_PROJECT_ID=
FIREBASE_STORAGE_BUCKET=
FIREBASE_MESSAGING_SENDER_ID=
```

.envファイルを作成して、firebaseの環境変数をコピペする

```..env
FIREBASE_API_KEY=****
FIREBASE_AUTH_DOMAIN=****
FIREBASE_DATABASE_URL=****
FIREBASE_PROJECT_ID=****
FIREBASE_STORAGE_BUCKET=****
FIREBASE_MESSAGING_SENDER_ID=****
```

README.mdに.envを作成するように追記

```README.md

# create .env file
cp .env.template .env

```

環境変数を読み込むようにするためにモジュールを追加

```
yarn add @nuxtjs/dotenv
```

nuxt.config.jsに追記

```nuxt.config.js
modules: [
  '@nuxtjs/dotenv'
]
```


### 参考
https://note.mu/yoneapp/n/n7037373c0b76
https://chatboxinc.wordpress.com/2018/03/26/nuxt_js_with_dotenv/


## FirebaseでAuthenticationの設定
Authenticationから「メールとパスワード」を有効にする。

ユーザ追加でテストユーザを作成しておく。

(後に独自ドメインを使う時に承認済みドメインに追加することを忘れない！）


## vue-persistedstateの導入
vuexはリロードすると値が消えてしまうため、vuex-persistedstateを導入したほうがいいらしい

https://github.com/robinvdvleuten/vuex-persistedstate

```
yarn add vuex-persistedstate
```

pluginsディレクトリ下にpersistedstate.jsを作成

```persistedstate.js
import createPersistedState from "vuex-persistedstate";

export default ({store, isHMR}) => {
  if (isHMR) return;

  if (process.client) {
    window.onNuxtReady((nuxt) => {
      createPersistedState()(store); // vuex plugins can be connected to store, even after creation
    });
  }
};
```

nuxt.config.jsに追記

```nuxt.config.js
plugins: [
    { src: "~plugins/persistedstate.js", ssr: false }
  ]
```

## core-jsのインストール(エラー対処）
なんか`yarn dev`するとエラーが出た。
`core-js`の2系を入れろとのこと。

というわけで、入れる...

```
yarn add core-js@2
```

とりあえず、`yarn.lock`と`node_modules`を削除してモジュールを入れ直す

```
rm -rf node_modules
rm yarn.lock
yarn install
```

動いた。(ここのissueでまとめられてる→https://github.com/nuxt/nuxt.js/issues/5313)

### 参考
- https://qiita.com/redshoga/items/da5c0e247e0df314a257
- https://qiita.com/sakapun/items/a0cf5698751ae70c8088
- https://ja.nuxtjs.org/examples/auth-routes/

## ログイン機能の実装
`pages`ディレクトリ下に`login.vue`を作成

```login.vue
<template>
  <div>
    <v-container fluid fill-height>
      <v-layout align-center justify-center>
        <v-flex xs12 sm8 md4>
          <v-card>
            <v-toolbar>
              <v-toolbar-title>Login</v-toolbar-title>
            </v-toolbar>
            <v-card-text>
              <v-form>
                <v-text-field
                  v-model="email"
                  :counter="32"
                  label="email"
                  prepend-icon="email"
                ></v-text-field>
                <v-text-field
                  v-model="password"
                  :append-icon="show_password ? 'visibility' : 'visibility_off'"
                  :type="show_password ? 'text' : 'password'"
                  :counter="32"
                  label="password"
                  prepend-icon="lock"
                  @click:append="show_password = !show_password"
                ></v-text-field>
              </v-form>
            </v-card-text>
            <v-card-actions>
              <v-btn
                flat
                v-on:click="gotoSignup"
              >SIGNUP</v-btn>
              <v-spacer></v-spacer>
              <v-btn
                v-on:click="doLogin"
              >LOGIN</v-btn>
            </v-card-actions>
            <hr>
            <v-spacer></v-spacer>
            <v-btn
              flat
              v-on:click="gotoResetPassword"
            >パスワードを忘れたかたはこちら</v-btn>
          </v-card>
        </v-flex>
      </v-layout>
    </v-container>
  </div>
</template>

<script>
import firebase from '~/plugins/firebase'

export default {
  data() {
    return {
      email: '',
      password: '',
      show_password: false,
    }
  },
  methods: {
    doLogin() {
      firebase.auth().signInWithEmailAndPassword(this.email, this.password)
        .then(user => {
          this.$router.push("/")
        }).catch((error) => {
          alert(error)
        })
    },
    gotoSignup() {
      this.$router.push("/signup")
    },
    gotoResetPassword() {
      this.$router.push("/reset-password")
    }
  }
}
</script>
```

とりあえずこんな感じ。

`pages`ディレクトリ下に`signup.vue`と`reset-password.vue`を作成しておく。（中身は適宜）


```js
firebase.auth().signInWithEmailAndPassword(this.email, this.password)
```

ここでfirebaseの認証をしている。

上で作成したユーザのメールアドレスとパスワードで認証ができるはず。


### 参考
https://vuetifyjs.com/ja/framework/pre-made-layouts


## storeの作成
storeでuserの情報を管理したいので、Nuxtに入っているVuexで状態管理をする。

### Vuexのコンセプト（簡単に）
State, Mutations, Actionを簡単に抑えておく。

#### State
stateとはstoreで管理している状態のことを言う。
Mutations以外から更新されることは無い。
Stateを取得するためにはgettersを使う。

#### Mutations
stateを更新するにはMutationsからcommitをする。
Mutationsは同期処理でなければならない。

#### Actions
データの加工や非同期処理はActionsで行い、Mutationsからcommitして更新をする。
Actionsの呼び出しにはdispatchをする。

## storeの作成
`store`ディレクトリ下に`user.js`を作成する。

```user.js
export const state = () => ({
  user: null,
  info: null,
})

export const mutations = {
  setUser (state, payload) {
    if (payload) {
      state.user = payload
    } else {
      state.user = null
    }
  },
  setInfo (state, payload) {
    if (payload) {
      state.info = payload
    } else {
      state.info = null
    }
  },
}

export const actions = {
  setUser ({ commit }, payload) {
    commit('setUser', payload)
  },
  setInfo ({ commit }, payload) {
    commit('setInfo', payload)
  },
}

export const getters = {
  user: state => {
    return state.user
  },
  info: state => {
    return state.info
  },
}
```

## ミドルウェアの作成
`middleware`ディレクトリ下に`authenicated.js`を作成する。

```authenicated.js
import firebase from '~/plugins/firebase'

export default function ({ store, route, redirect }) {
  firebase.auth().onAuthStateChanged(user => {
    if (!user) {
      redirect('/account/login')
    } else {
      store.commit('user/setUser', {
        uid: user.uid,
        email: user.email,
        username: user.displayName,
        userImage: user.photoURL,
      })
    }
  })
}
```

こんな感じで。

コンポーネントの`export default`内で（dataやmethodsと同列)、`middleware: 'authenticated',`を記述すると動く。


## おわりに
ここまでで、プロジェクトの作成からFirebase認証でログイン機能の実装、ミドルウェアの実装までを行ってきました。
次はheaderやfooterのlayoutを作成したり、storeを使って値を参照したりしようと思います。