Firebase v9 + Vue3 + Vite + Vuetify3 beta で firebase auth のメール認証でログインしてみるの、TypeScript版

# Firebase v9 + Vue3 + Vite + Vuetify3 beta で firebase auth のメール認証でログインしてみるの、TypeScript版

元のQiita：[Firebase v9 + Vue3 + Vite + Vuetify3 beta で firebase auth のメール認証でログインしてみる](https://qiita.com/mml/items/a3ae14673bad2f15af06) をTypeScript版でうまくいくようにやってみましょうという事です。

ちなみに筆者は、Vue2とVuetify2を少し触ってたら、時代がVue3になっていて驚いている人、世間様があまりにもTypeScriptというもんだから、TypeScriptを学習初めたばかりのものです。つまりはどちらも慣れてはおりません。これから学んで慣れていければと思っております。

## 変更した所のまとめ

|                  | js                     | ts                        |
| ---------------- | ---------------------- | ------------------------- |
| アプリ作成            | vue-create app         | yarn create vite          |
| vue-routerインストール | vue add router         |  yarn add vue-router@4 |
| 各種ファイル           | firebase.js, router.js | firebase.ts, router.ts    |

## Vue関連のインストール

### Vue3のアプリの作成

参考：[Vite + Vue3にESLintとPrettierを導入する](https://zenn.dev/chida/articles/c0bd3ad56ed06b)

vue-createでなく、yarn create viteを使う

```shell
yarn create vite
? Project name: vue-ts-app
? Select a framework > vue
? Select a variant > vue-ts

cd vue-ts-app
yarn
yarn dev
```
### Vuetify3の追加

```shell
vue add vuetify
? Choose a preset: > Vite Preview (Vuetify 3 + Vite)
yarn dev
```

Welcome to Vuetify 3 Beta Vite Previewというページを見ることができました。

### vue-routerのインストール

参考：[Vue3 + TypeScript + Vue RouterからはじめるWebアプリケーション開発](https://note.com/taatn0te/n/nd7940041135e)

`vue add router`は使わない

```shell
yarn add vue-router@4
```
### yarn以外のパッケージマネージャーを使うと

* `npm`を使った場合、`vue add vuetify`後は、なぜかnpm install がエラーとなる場合がありました。
* `pnpm`だと`vue add vuetify`自体でエラーが出てしまいそこから進めなくなりました。他の`vue add`でも同様とのIssueがあがっていました。
* すべて`vue add`が鬼門のもようです。

### routerの設定
#### src/router.tsの作成
ViteのCLIがrouterをインストールしたら色々設定してくれるということは無いので、色々手作業で設定する必要があります。

ここでは、'/'にアクセスされると、現在はまだ存在しないファイルsrc/views/HomeView.vueを読むという設定にしています。

参考：[Vite で作成したアプリのVue-routerの設定方法](https://zenn.dev/takpon1751/articles/83135cc6a0fc9f)

```typescript:src/router.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('./views/HomeView.vue'),
  },
]

const router = createRouter({
  history: createWebHistory(), // HTML5 History モード
  routes,
})

export default router
```

#### src/main.tsに追加する

```typescript:src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
import router from './router' // この行を追加

loadFonts()

// ここらへ.use(router)を追加
createApp(App)
  .use(vuetify)
  .use(router)
  .mount('#app')

```
#### App.vueの変更
routerで飛ばされるだけの存在にしてみます。


```vuejs:src/App.vue
<template>
  <v-app>
    <router-view />
  </v-app>
</template>
```

#### src/views/HomeView.vueを作る
`mkdir -p src/views`

```vuejs:src/views/HomeView.vue
<template>
  <v-app>HOME(HomeView)</v-app>
</template>
```
#### routerの動作確認
`yarn dev`

出てきたリンクにアクセスしてHOME(HomeView)のみ表示されるシンプルなものが見えたら動作確認完了です。

## firebase

### 初期化

firebase initして、必要なサービスを選び、Firebaseのプロジェクトに紐付ける。そしてそのプロジェクトでは、initした時に選択したサービスはFirebase consoleで開始しておく必要がある。Authenticationではログインプロバイダで、メール/パスワードは使えるようにしておく必要がありました。

```shell
firebase init
? Which Firebase features do you want to set up for this directory?...
Hosting: Set up GitHub Action deploys以外を全部選択してみました
? Please select an option: > Use an existing project
? Select a default Firebase project for this directory: vue3ts-123456 (Vue3ts)
? What file should be used for Realtime Database Security Rules? database.rules.json
? What file should be used for Firestore Rules? firestore.rules
? What file should be used for Firestore indexes? firestore.indexes.json
? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
```

Hosting Setupの? What do you want to use as your public directory? だけはデフォルトとは全然違う回答をしているので注意、distにしています。

Emulators Setupは全部選択しました。
Emulatorsのportは全部デフォルトでエンターを押しました

```shell
? What do you want to use as your public directory? dist
? Configure as a single-page app (rewrite all urls to /index.html)? No
? Set up automatic builds and deploys with GitHub? No
? What file should be used for Storage Rules? storage.rules
? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. 全部
EmulatorsのPortは全部デフォルトに
? Would you like to enable the Emulator UI? Yes
? Which port do you want to use for the Emulator UI (leave empty to use any available port)?
? Would you like to download the emulators now? No
? What file should be used for your Remote Config template? remoteconfig.template.json
```

### APIキーなどの登録
#### 環境変数を使う

参考：[【Vue.js】VueCLI3.x 4.xで環境変数を扱う場合は .env ファイルに VUE_APP_ の接頭語をつける](https://kic-yuuki.hatenablog.com/entry/2019/11/17/171058)

* プロジェクトルートに.env.localというファイルを作成し、そこに環境変数を書きFirebaseのAPIキーなど、外部に渡したくないものを書くと、Vue3は自動的に.env.localを読み込み、環境変数に設定しました。

* 環境変数の接頭辞はVUE_APP_としないと読み込みません。

* .env.localファイルを.gitignoreに登録しました。

この辺りを手で書いても良いのですがそれを楽にするツールを使うことにします。firebaseで使うプロジェクトから、web app's Firebase configurationをコピーまたはファイルに保存します。

そして、npmによるcliツール@hkj_50/firebase-configを実行して質問に答え、src/firebase.tsと.env.localファイルを生成します。

```shell
npx @hkj_50/firebase-Config

? Where is the root dir of the your App? ./
? Enviroment Variables file name? .env.local
? Do you use firebase's emulator? Yes
? In which format should the contents of firebaseConfig be entered? file
? Where is the path to the file containing firebaseConfig? ../firebaseConfig.js
```
上記の場合は、プロジェクトルート上で`npx @hkj_50/firebase-config`を実行し、web app's Firebase configurationを`../firebaseConfig.js`という名前で保存していたので、それを指定しています。エミュレータを使うと指定しました。その結果、`src/firebase.ts`と`.env.local`という二つのファイルが生成されました。

```typescript:src/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
import { getFirestore, connectFirestoreEmulator } from "firebase/firestore";

import { getStorage, connectStorageEmulator } from "firebase/storage";
import { getAuth, connectAuthEmulator } from "firebase/auth";

// Your web app's Firebase configuration
const firebaseConfig = {
  apiKey: 'process.env.VUE_APP_API_KEY',
  authDomain: 'process.env.VUE_APP_AUTH_DOMAIN',
  databaseURL: 'process.env.VUE_APP_DATABASE_URL',
  projectId: 'process.env.VUE_APP_PROJECT_ID',
  storageBucket: 'process.env.VUE_APP_STORAGE_BUCKET',
  messagingSenderId: 'process.env.VUE_APP_MESSAGING_SENDER_ID',
  appId: 'process.env.VUE_APP_APP_ID',
}

const firebase = initializeApp(firebaseConfig);
const isEmulating = window.location.hostname === "localhost";
if (isEmulating) {

    const auth = getAuth();
    connectAuthEmulator(auth, "http://localhost:9099");

    const storage = getStorage();
    connectStorageEmulator(storage, "localhost", 9199);

    const db = getFirestore(firebase);
    connectFirestoreEmulator(db, 'localhost', 8080);

    const functions = getFunctions(firebase);
    connectFunctionsEmulator(functions, "localhost", 5001);
}

export const firebaseApp = () => { return firebase };

export default firebase;
```
肝心な情報は環境変数から読み込まれ、このファイルには書かれていないので、gitに含めたり、githubにて公開されても問題ありません。

```bash:.env.local
VUE_APP_API_KEY="YourApiKey"
VUE_APP_AUTH_DOMAIN="yourAuthDomain.firebaseapp.com"
VUE_APP_DATABASE_URL="https://yourdomain.firebaseio.com"
VUE_APP_PROJECT_ID="yourID-12345"
VUE_APP_STORAGE_BUCKET="youID-12345.appspot.com"
VUE_APP_MESSAGING_SENDER_ID="123456789012"
VUE_APP_APP_ID="1:123456789012:web:a11bbcc123456ab1a1b23b"
```
こちらは肝心な情報なので、.gitignoreに入っていない場合は登録します。

`echo '*.local' >> .gitignore`

### tsconfig.jsonの設定
参考:[TypeScript に対応した Vue 3 プロジェクト の作成](https://www.harubears.com/ja/tech-ja/vue-js-ja/project-creation-with-typescript/)

その項目が無ければ、"compilerOptions"以下に、"baseUrl"と"paths"の設定を追加します。

```json:tsconfig.json
{
  "compilerOptions": {
    ...
    ...
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  ...
  ...
}
```

### firebase関連のインストール

```shell
yarn add firebase
```
確かにこれは忘れやすいと、私も思いました。

### src/App.vueにfirebaseApp登録を追加
```vuejs:src/App.vue
<template>
  <v-app>
    <router-view/>
  </v-app>
</template>
<script setup lang="ts">
import { firebaseApp } from './firebase';
const app = firebaseApp()
</script>
```
このfirebaseApp()を呼ばないとfirebaseを初期化していないと怒られる現象が後に起こります。

### Firebaseを使った機能の実装例

Homeview.vueに実装してみます。元々の書き方がComposition APIっぽくない書き方をしていたので、Composition API風に書き替えています。`<script setup lang>`を使う事によって、余計な呪文を排除して、scriptとtemplateとstyleのみとなったすっきりした書き方ができるようになります。

Refで動的に変更する変数を定義します。constで定義していますが、refのオブジェクトになり、変数.valueという形で実際の値を使い、そのvalueは変更可能です。

mounted()はonMounted()内に書きます。

v-if="currenUser.value === null"と書きたかったのですが、nullである可能性があるとなってエラーとなりました。v-if="currentUser === null"であれば、エラーとならず、動作の目的には叶いました。しかし、本当にこれで良いのかは良く分かっておりません。


```vuejs:src/views/HomeView.vue
<script setup lang="ts">
import { ref, Ref, onMounted } from 'vue'
import {
  getAuth,
  signOut,
  onAuthStateChanged,
  signInWithEmailAndPassword,
  createUserWithEmailAndPassword,
  User,
} from 'firebase/auth'

const emailText: Ref<string> = ref('')
const passwordText: Ref<string> = ref('')
const message: Ref<string> = ref('')
const currentUser: Ref<User | null> = ref(null)

onMounted(() => {
  const auth = getAuth()
  onAuthStateChanged(auth, (user) => {
    if (user != null) {
      currentUser.value = user
    } else {
      currentUser.value = null
    }
  })
})

const signIn = () => {
  // エラーなどメッセージをクリア
  message.value = ''
  if (emailText.value == '' || passwordText.value == '') return
  const auth = getAuth()
  signInWithEmailAndPassword(auth, emailText.value, passwordText.value)
    .then((userCredential) => {
      // Sign In
      const user: User = userCredential.user
      console.log(user)
    })
    .catch((error) => {
      message.value = error.message
      console.log(error.code, error.message)
    })
}

const createAccount = () => {
  message.value = ''
  if (emailText.value == '' || passwordText.value == '') return
  const auth = getAuth()
  createUserWithEmailAndPassword(auth, emailText.value, passwordText.value)
    .then((userCredential) => {
      const user: User = userCredential.user
      console.log(user)
    })
    .catch((error) => {
      message.value = error.message
      console.log(error.code, error.message)
    })
}

const logOut = () => {
  const auth = getAuth()
  signOut(auth)
    .then(() => {
      currentUser.value = null
    })
    .catch((error): void => {
      console.log(error)
    })
}
</script>

<template>
  <v-container>
    <div v-if="currentUser === null">
      <v-card width="400px" class="mx-auto mt-5">
        <v-card-actions>
          <v-col>
            <v-text-field
              v-model="emailText"
              label="E-MAIL"
            >
            </v-text-field>
            <v-text-field
              v-model="passwordText"
              label="PASSWORD"
              type="password"
            >
            </v-text-field>
            <v-btn @click="signIn" color="primary"> E-Mail Sign In </v-btn>
            <v-btn color="primary" @click="createAccount">Sign Up</v-btn>
            <div class="message">{{ message }}</div>
          </v-col>
        </v-card-actions>
      </v-card>
    </div>
    <div v-else>
      <h2>Success to sign in with firebase auth!</h2>
      <v-btn @click="logOut">Sign Out</v-btn>
    </div>
  </v-container>
</template>

<style scoped>
.message {
  color: red;
}
</style>
```
### Functionsの設定
必要なライブラリが入ってないのとビルドがされていないので実行
```shell
cd functions
yarn
yarn build
```
buildで
```shell
rc/index.ts:1:1 - error TS6133: 'functions' is declared but its value is never read.

1 import * as functions from "firebase-functions";
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
と怒られました。現時点ではfunctionsは使わないので、コメントアウトしておきます。

`// import * as functions from "firebase-functions";`
そこで再びbuild
`yarn build`
エラーが出ずに完了しました。

### Firebase Emulatorの実行と確認
`firebase emulators:start`

とやってから違うターミナルで

`yarn dev`

とやってください。E-MAILとPASSWORDの所に入れて初めてであれば、SIGN UP、そうでなければ、E-MAIL SIGN INを選んでいただいて、`Success to sign in with firebase auth!`と出て、SIGN OUTボタンを押せば元に戻れば成功です。