開発環境
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>
これまたほぼコピペ。