Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発

福岡から世界中の"むずかしい"を簡単にする株式会社[diffeasy](https://diffeasy.com)CTOの西[@_takeshi_24](https://twitter.com/_takeshi_24)です。

公開から1年以上が経ち、この記事の内容も少し古くなってきました。

Nuxt.jsやFirebaseを使って、サーバーレスでWebアプリケーションを開発する手順について、アドベントカレンダー「[diffeasyCTO西の24(にし)日連続投稿チャレンジ Advent Calendar 2019](https://qiita.com/advent-calendar/2019/diffeasy24)」の中で、「Nuxt.jsとFirebaseとCloudFunctionsでWebアプリ開発」シリーズとして、連載していきます。
Nuxt.jsとFirebaseなどを使ってWebアプリケーション開発にチャレンジしたい方、是非Qiitaアカウントか[twitterをフォロー](https://twitter.com/_takeshi_24)していただき、ツッコミやいいね！お願いします！

# 言葉の説明
まずはNuxt、Firebase、PWAの説明です。
ご存知の方はすっ飛ばしてください！

## Nuxt
Vue.js アプリケーションを構築するためのフレームワークです。
[こちらの記事](https://qiita.com/_takeshi_24/items/224d00e5a026dbb76716)に少し詳しく、[公式ドキュメント](https://nuxtjs.org/)にさらに詳しい情報が載っていますので、そちらをご覧ください。
2018年9月21日にVer2がリリースされました。

## Firebase
Googleが提供するBaaS(Backend as a Service)です。
Webフロントエンド、ネイティブアプリからFirebaseの機能を利用することで、認証、ストレージの管理、データベースの管理など、従来サーバーサイドで開発していたロジックなしにアプリケーションの開発が可能です。
<img width="320" alt="スクリーンショット 2018-10-13 17.29.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/91bb2ead-4a64-7c92-bd55-94b8d72fa3bd.png">
↓↓↓↓↓
<img width="320" alt="スクリーンショット 2018-10-13 17.29.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/8aef45da-9be3-1387-e1a7-62a495199f31.png">


[Firebase使ってみた](https://qiita.com/_takeshi_24/items/419b9d0d879351f641f3)
[Firebase公式](https://firebase.google.com/docs/)

## PWA
PWA = Progressive Web Apps（プログレッシブウェブアプリ）のことで、アプリのリンクをホーム画面に残しておくことで、あたかもネイティブアプリケーションのように動作可能なWebアプリケーションのことを言います。
オフラインでも動作可能で、PUSH通知も可能です。

[はじめてのプログレッシブ ウェブアプリ](https://developers.google.com/web/fundamentals/codelabs/your-first-pwapp/?hl=ja)

# Nuxt v2とFirebase(CloudFirestore)でPWA対応Webアプリ開発
ここからが本題です！
ソースは[Github](https://github.com/tkc24/nuxt2_firebase_chat)にあげています。

## 今回作ったアプリ
Nuxt v2とFirebaseを使ってPWA対応の簡易リアルタイムチャットWebアプリを作ります。
![nuxt2_chat.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/3003/cfb4cc46-8c6d-00be-764d-a7955131d141.gif)

## フォルダ構成
フォルダ構成は以下のようになります。
実際のフォルダの場所は開発環境に合わせて読み替えてください。

```
~
 ∟ firebase_app
   ∟ nuxt2_chat
```


## Nuxt v2でアプリ開発
npmを使って構築もできますが、今回はyarnを使って、scaffoldingツールでNuxtアプリケーションを構築します。

```shell-session
$ cd ~
$ mkdir ./firebase_app
$ cd ./firebase_app
```

```shell-session
$ yarn create nuxt-app nuxt2_chat #nuxt2_chatはアプリ名
```

アプリ名や各種フレームワーク、ツールの利用を問われますので、必要に応じて選択します。

```shell-session
? Project name <アプリ名>
? Project description <アプリ説明>
? Use a custom server framework none #サーバーフレームワークは指定しない
? Use a custom UI framework vuetify　#UIフレームワークはvuetifyを利用
? Choose rendering mode Single Page App #SPAモードを指定
? Use axios module no #axiosを利用
? Use eslint yes #eslintを利用
? Use prettier yes #prettierを利用
? Author name <作成者名>
? Choose a package manager yarn #yarnでパッケージ管理
・・・
success Saved lockfile.
✨  Done in 32.75s.
```

今回はテンプレートエンジンにpugを使うので、pugとpug-plain-loaderをインストールします。
※Nuxt v1ではpug-loaderでしたが、v2ではpug-plain-loaderを利用する必要があります。

```shell-session
$ cd ~/firebase_app/nuxt2_chat
$ yarn add pug pug-plain-loader --dev
```

開発用のサーバーを立ち上げます。

```shell-session
$ yarn run dev
```

http://localhost:3000
にアクセスすると、Vuetifyが適用されたNuxtアプリケーションが表示されます。
<img width="1440" alt="スクリーンショット 2018-10-12 18.00.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/f859b07f-e3c8-a898-d3a4-40ff1de05a94.png">


## Firebaseの設定
[Firebaseのサイト](https://console.firebase.google.com)から新規プロジェクトを作成します。
<img width="1342" alt="Firebaseサイト" src="https://qiita-image-store.s3.amazonaws.com/0/3003/dd5103f4-6348-2b1d-bb8c-374f7bda7c6c.png">

「プロジェクトを追加」をクリックして、プロジェクト名を入力して、「プロジェクトを作成」をクリックします。
<img width="420" alt="プロジェクトを追加" src="https://qiita-image-store.s3.amazonaws.com/0/3003/ac5175a9-7bb3-7260-0a98-e31c32a5ac11.png">

Firebaseには色々な機能が用意されていますが、今回利用するのは、

- Authentication
- Database(Cloud Firestore)
- Hosting

の３つです。

Firebaseコンソールトップの「開始するにはアプリを追加してください」の上の「</>」をクリックして、アプリから接続するための情報を取得します。
<img width="648" alt="Firebase接続情報" src="https://qiita-image-store.s3.amazonaws.com/0/3003/518dc07d-cd36-d25c-66f1-cee408213106.png">
この値は後ほど使います。


### Authentication
Authenticationに遷移して、「ログイン方法を設定」をクリックします。
<img width="1440" alt="スクリーンショット 2018-10-12 18.18.53.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/0148cea2-46db-a2b9-e51d-2b01453c369e.png">
「メール/パスワード」でのログインを有効にします。
<img width="975" alt="スクリーンショット 2018-10-12 18.19.23.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/f8cb8166-3c8b-4518-be9c-5593e80b077c.png">
テスト用にユーザーを追加します。
<img width="980" alt="スクリーンショット 2018-10-12 18.23.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/8577b7b2-cb6e-b5de-dafa-6919271f0b3e.png">

### Cloud Firestore
Firebaseのデータベースには、Realtime DatabaseとCloud Firestore(β版)の２つがありますが、今回はCloud Firestoreを利用します。

セキュリティルールは後から編集できますが、ひとまず「テストモードで開始」を選択します。
<img width="685" alt="スクリーンショット 2018-10-12 18.41.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/45f39b5b-9a34-f8c3-e573-e5a53e8956c3.png">


### Hosting
Hostingに遷移して、「使ってみる」をクリックします。
<img width="1440" alt="スクリーンショット 2018-10-12 18.43.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/3003/71effdbe-7f7a-0c0c-14bc-310cef75f397.png">


### ローカルにFirebaseの開発環境を作る
firebase-toolsをインストールします。

```shell-session
$ cd ~/firebase_app
$ npm install -g firebase-tools
```

プロジェクトを初期化します。

```shell-session
$ firebase init
```

Hostingを選択します。
<img width="967" alt="firebase init" src="https://qiita-image-store.s3.amazonaws.com/0/3003/3243ffd1-135c-f8e1-2645-b4a200c1c3a7.png">

先ほど作成したFirebaseのプロジェクトを選択します。

```shell-session
? Select a default Firebase project for this directory: nuxt2-chat (nuxt2-chat)
```

公開するフォルダを指定します。
先ほど作成したNuxtプロジェクトのdistを選択します。

```shell-session
? What do you want to use as your public directory? (public) nuxt2_chat/dist
```

### NuxtとFirebaseを連携
NuxtとFirebaseとの連携にvuexfireを利用します。
Firebaseの接続情報はソース管理から外したいので、.envを利用します。

```shell-session
$ yarn add @nuxtjs/dotenv firebase vuexfire@next --save
```
**※2018年10月12日時点では、vuexfireではCloud Firestoreのbindでエラーになったので、vuexfire@nextをインストールしています。**

.envファイルにFirebaseのAPIキーなど接続情報を記述します。

```/.env
FB_API_KEY=<FirebaseAPIキー>
FB_AUTH_DOMAIN=<Firebse プロジェクトID>.firebaseapp.com
FB_DATABASE_URL=https://<Firebse プロジェクトID>.firebaseio.com
FB_PROJECTID=<Firebse プロジェクトID>
FB_STORAGE_BUCKET=<Firebse プロジェクトID>.appspot.com
FB_MESSAGING_SENDER_ID=<Firebase messaging　通知送信キー>
```

plugins/firebase.jsでFirebaseとの接続情報を取得し、初期化します。

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

var config = {
  apiKey: process.env.FB_API_KEY,
  authDomain: process.env.FB_AUTH_DOMAIN,
  databaseURL: process.env.FB_DATABASE_URL,
  projectId: process.env.FB_PROJECTID,
  storageBucket: process.env.FB_STORAGE_BUCKET,
  messagingSenderId: process.env.FB_MESSAGING_SENDER_ID
}
if (!firebase.apps.length) {
  firebase.initializeApp(config)
}

export default firebase
```

Firebaseの認証機能を利用するため、plugins.auth.jsに以下のファイルを作成します。

```js:/plugins/auth.js
import firebase from '~/plugins/firebase'

function auth() {
  return new Promise((resolve, reject) => {
    firebase.auth().onAuthStateChanged(user => {
      resolve(user || false)
    })
  })
}
export default auth
```

nuxt.config.jsでplugins/firebase、plugins/authと.envを読み込むよう、以下の設定を追加します。

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

  modules: [
    '@nuxtjs/dotenv'
  ],
```

### 認証処理

```js
auth.signInWithEmailAndPassword (data['email'], data['password'])
.then((user) => {
    // ログイン成功
    commit('setUser', user)})
.catch((error) => {
   　// エラー処理
})
```

### Firestoreに書き込み

```js
import 'firebase/firestore'
const db = firebase.firestore()
const messagesRef = db.collection('messages')
export const state = () => ({
  messages: [],
})
 export const actions = {
  addMessageRef: firebaseAction((context, data) => {
    messagesRef.add({
      uid: data.uid,
      text: data.text,
      time: Date.now()
    })
  })
}
```

### Firestoreから読み込み

```js
import 'firebase/firestore'
const db = firebase.firestore()
const messagesRef = db.collection('messages')
export const state = () => ({
  messages: [],
})
 export const actions = {
  initMessages: firebaseAction(async ({ bindFirebaseRef }) => {
    await bindFirebaseRef('messages', messagesRef.orderBy('time', 'desc'))
  })
}
```

詳細なソースは[Github](https://github.com/tkc24/nuxt2_firebase_chat)にあげていますので、そちらをご覧ください！

## PWA対応
PWA対応することで、スマホホーム画面にアプリを追加するように促してくれます。
ホームにアプリを追加するときのアイコン画像を
/static/icon.png
に置きます。

```shell-session
yarn add @nuxtjs/pwa --save
```

```js:/nuxt.config.js
modules: [
  '@nuxtjs/dotenv',
  '@nuxtjs/pwa'
],
manifest: {
  name: '<アプリ名>',
  short_name: '<アプリ略称>',
  lang: 'ja'
},
```

```/.gitignore
# Service Worker
sw.*
```

## Firebase Hostingにデプロイ
Nuxtプロジェクトをgenerateします。

```shell-session
$ cd ~/firebase_app/nuxt2_chat
$ yarn run generate
```

generateしたファイルをFirebase Hostingにデプロイします。

```shell-session
$ cd ~/firebase_app
$ firebase deploy
```

スマホからアクセスすると、以下のようにホームに追加するためのナビゲーションが表示されます。
<img width="320" alt="スマホ表示1" src="https://qiita-image-store.s3.amazonaws.com/0/3003/65da1629-0f8d-6369-0388-ee4b4d020369.jpeg">
<img width="320" alt="スマホ表示2" src="https://qiita-image-store.s3.amazonaws.com/0/3003/afc8d4e7-abf6-e172-3298-8ac7478d5701.jpeg">

ホームに追加したアイコンから起動すると、ブラウザのバーが非表示になり、ネイティブアプリのように利用することができます。
<img width="320" alt="スマホ表示1" src="https://qiita-image-store.s3.amazonaws.com/0/3003/c1f06a59-552a-934e-357f-21c1dbd2e72f.jpeg">

さらに、オフライン時でもエラーになることなく、画面からメッセージの投稿が可能です。
投稿したメッセージは再びオンラインになった時に自動的にサーバーに送信されます。


## プッシュ通知
Webアプリケーションへのプッシュ通知について、以下の記事にまとめました！
[NuxtでPWA対応WebアプリにOneSignalからプッシュ通知を送る](https://qiita.com/_takeshi_24/items/57d68790d6ec88140099)