vue.js
Firebase
nuxt.js

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


開発環境

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
$ yarn add -D nuxt-sass-resources-loader
$ 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: [
],
generate: {
},
sassResources: [
],
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>


これまたほぼコピペ。