LoginSignup
10
8

More than 5 years have passed since last update.

Nuxt.js + Firebaseでプロジェクト作る

Last updated at Posted at 2019-01-23

開発環境

node v10.11.0
yarn 1.10.1

はじめに

この記事の内容は

Vue.js+Firebaseで認証付きチャット
Nuxt.jsの開発環境構築の手順(最初の一歩)
Web開発が捗るFirebase入門!JavaScriptで「Webユーザー認証」機能を超お手軽に作るチュートリアル大公開!

を自分なりに掛け合わせて、使えるようにした記録です。

手順

0. firebase登録・RealtimeDatabase設定

「Vue.js+Firebaseで認証付きチャット | 基礎から学ぶ Vue.js」
https://cr-vue.mio3io.com/tutorials/firebase.html
参照。

1. nuxtアプリ作成・各種インストール

$ yarn create nuxt-app <my-project>
$ yarn add -D pug@2.0.3 pug-plain-loader node-sass sass-loader @nuxtjs/style-resources
$ yarn add firebase
$ yarn add vue-nl2br #改行をbrタグに変換する

2. nuxt.config.js編集

nuxt.config.js
const pkg = require('./package')

module.exports = {
  mode: 'universal',
  head: {
    title: pkg.name,
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: pkg.description }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  loading: { color: '#fff' },
  plugins: [
    { src: '~/plugins/global-components.js', ssr: true },
  ],
  css: [
  ],
  webfontloader: {
    google: {
      families: ['Noto+Sans+JP']
    }
  },
  modules: ['@nuxtjs/style-resources', '@nuxtjs/axios', '@nuxtjs/pwa'],
  generate: {
  },
  stykeResources: {
    sass:[],
  },
  build: {
    extend(config, ctx) {
    }
  }
}

firebaseの情報はグローバルコンポーネントとして切り分ける。

$ yarn generate #ページ生成
$ yarn dev #サーバ起動

3. global-component.jsにfirebase情報切り分け

global-component.js
import Vue from 'vue'

// firebase
import firebase from 'firebase'
Vue.config.productionTip = false
const config = {
  apiKey: "AIza....",
  authDomain: "YOUR_APP.firebaseapp.com",
  databaseURL: "https://YOUR_APP.firebaseio.com",
  projectId: "YOUR_APP",
  storageBucket: "YOUR_APP.appspot.com",
  messagingSenderId: "123456789"
}
firebase.initializeApp(config)

//default components
import LoginForm from '~/components/LoginForm.vue'
Vue.component('LoginForm',LoginForm)

4. chat.vue作成

chat.vue
<template lang="pug">
  #app
    header.header
      h1 Chat
      div(v-if="user.uid" key="login")
        |[ {{ user.email }} ]
        button(type="button" @click="doLogout") ログアウト
      div(v-else key="logout")
        LoginForm
        // button(type="button" @click="doLogin") ログイン
    transition-group(name="chat" tag="div" class="list content")
      section(v-for="{ key, name, image, message } in chat" :key="key" class="item")
        .item-image
          img(:src="image" width="40" height="40")
        .item-detail
          .item-name {{ name }}
          .item-message
            nl2br(tag="div" :text="message")
    form(action="" @submit.prevent="doSend" class="form")
      textarea(
        v-model="input"
        :disabled="!user.uid"
        @keydown.enter.exact.prevent="doSend"
      )
      button(type="submit" :disabled="!user.uid" class="send-button") Send
</template>

<script>
  // firebase モジュール
  import firebase from 'firebase'
  // 改行を <br> タグに変換するモジュール
  import Nl2br from 'vue-nl2br'
  export default {
    components: { Nl2br },
    data() {
      return {
        user: {},  // ユーザー情報
        chat: [],  // 取得したメッセージを入れる配列
        input: ''  // 入力したメッセージ
      }
    },
    created() {
      firebase.auth().onAuthStateChanged(user => {
        this.user = user ? user : {}
        const ref_message = firebase.database().ref('message')
        if (user) {
          this.chat = []
          // message に変更があったときのハンドラを登録
          ref_message.limitToLast(10).on('child_added', this.childAdded)
        } else {
          // message に変更があったときのハンドラを解除
          ref_message.limitToLast(10).off('child_added', this.childAdded)
        }
      })
    },
    methods: {
      // ログアウト処理
      doLogout() {
        firebase.auth().signOut()
      },
      // スクロール位置を一番下に移動
      scrollBottom() {
        this.$nextTick(() => {
          window.scrollTo(0, document.body.clientHeight)
        })
      },
      // 受け取ったメッセージをchatに追加
      // データベースに新しい要素が追加されると随時呼び出される
      childAdded(snap) {
        const message = snap.val()
        this.chat.push({
          key: snap.key,
          name: message.name,
          image: message.image,
          message: message.message
        })
        this.scrollBottom()
      },
      doSend() {
        if (this.user.uid && this.input.length) {
          // firebase にメッセージを追加
          firebase.database().ref('message').push({
            message: this.input,
            name: this.user.email,
            image: this.user.photoURL
          }, () => {
            this.input = '' // フォームを空にする
          })
        }
      }
    }
  }
</script>

<style lang="scss">
  * {
    margin: 0;
    box-sizing: border-box;
  }
  .header {
    background: #3ab383;
    margin-bottom: 1em;
    padding: 0.4em 0.8em;
    color: #fff;
  }
  .content {
    margin: 0 auto;
    padding: 0 10px;
    max-width: 600px;
  }
  .form {
    position: fixed;
    display: flex;
    justify-content: center;
    align-items: center;
    bottom: 0;
    height: 80px;
    width: 100%;
    background: #f5f5f5;
  }
  .form textarea {
    border: 1px solid #ccc;
    border-radius: 2px;
    height: 4em;
    width: calc(100% - 6em);
    resize: none;
  }
  .list {
    margin-bottom: 100px;
  }
  .item {
    position: relative;
    display: flex;
    align-items: flex-end;
    margin-bottom: 0.8em;
  }
  .item-image img {
    border-radius: 20px;
    vertical-align: top;
  }
  .item-detail {
    margin: 0 0 0 1.4em;
  }
  .item-name {
    font-size: 75%;
  }
  .item-message {
    position: relative;
    display: inline-block;
    padding: 0.8em;
    background: #deefe8;
    border-radius: 4px;
    line-height: 1.2em;
  }
  .item-message::before {
    position: absolute;
    content: " ";
    display: block;
    left: -16px;
    bottom: 12px;
    border: 4px solid transparent;
    border-right: 12px solid #deefe8;
  }
  .send-button {
    height: 4em;
  }
  /* トランジション用スタイル */
  .chat-enter-active {
    transition: all 1s;
  }
  .chat-enter {
    opacity: 0;
    transform: translateX(-1em);
  }
</style>

ほぼコピペ。
ログインフォームのみ、LoginForm.vueにコンポーネントとして切り分ける。

5. LoginForm.vue(component)作成

LoginForm.vue
<template lang="pug">
  #input-area
    input#email(type="email" placeholder="メールアドレスを入力" required)
    input#password(type="password" placeholder="パスワードを入力" required)
    button(@click="doLogin") ログイン
    button(@click="userCreate") 新規登録
</template>

<script>

  import firebase from 'firebase'
  export default {
    methods: {
      userCreate(){
        const email = document.getElementById('email').value;
        const password = document.getElementById('password').value;
        firebase.auth().createUserWithEmailAndPassword(email, password)
        .catch(function(error) {
          alert('登録できません(' + error.message + '');
        });
      },
      doLogin(){
        const email = document.getElementById('email').value;
        const password = document.getElementById('password').value;
        firebase.auth().signInWithEmailAndPassword(email, password)
        .catch(function(error) {
          alert('ログインできません(' + error.message + '');
        });
      }
    }
  }
</script>

<style lang="scss">
</style>

これまたほぼコピペ。

10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8