Svelte + Firebaseを使ってチャットツールを作ってみた

## はじめに
突然ですが、今まで触れたことのないフロントエンドフレームワークを使ってみたいと思いました。  
とある飲み会で「Svelteいいですよ！」と推しを受けたので、Svelteでなにか作ってみようと思い立ちました。  
何かいいネタがないかと社内で相談したところ、Firebaseを使ってチャットツールを作る案をもらいました。  

ということで、かなり軽いきっかけですがSvelte + Firebaseを使ってチャットツールを作ってみました。  
本記事では主に、作った際に調べた内容を記載していきます。    

## Svelteとは
[Svelte](https://svelte.jp/)は、2016年から開発が開始されたWebフロントエンドフレームワークです。  
[State of JavaScript 2022](https://2022.stateofjs.com/en-US/libraries/front-end-frameworks/)によると利用率は第4位で、React / Angular / Vue.jsに次いでいます。  

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341817/87ba4826-1f42-6e86-a43e-196ba8e176cb.png" width="500">

また、公式サイトに「サイバネティクスで強化されたwebアプリ」とのウリ文句がありました。  
ちょっとよくわからなかったので、もう少し調べたら[以下の記載](https://svelte.jp/blog/svelte-3-rethinking-reactivity#svelte-new-look)が。

> また、私たちはキャッチフレーズを「魔法のように消える UI フレームワーク」から「サイバネティックに強化されたウェブアプリ」に変更しました。  
> Svelte には様々な側面があります。-- 卓越した性能、小さなバンドル、アクセシビリティ、組み込みのスタイルカプセル化、宣言的な遷移、使いやすさ、コンパイラであることなど --  
> その中のどれか1つに焦点を当てることは、他のものに不公平感を与えるような気がします。「Cybernetically enhanced」は、私たちのツールは私たち自身の知能の拡張として機能するべきだという Svelte の包括的な哲学を思い起こさせるようにデザインされています  
> -- 願わくばレトロでウィリアム・ギブソン風のひねりを加えて。

...うーんこれは哲学🤔  
「どれか1つではなく、全て良くなるように作っているよ」と言っているのだなと理解しました。  

## SvelteKitプロジェクトの作成
Svelteで実装を行うために、[SvelteKit](https://kit.svelte.jp/)プロジェクト作成手順を記載します。  
SvelteとSvelteKitは、Vue.jsとNuxt.jsの関係に似ていて、SvelteKitではサーバサイドの処理やサーバサイドレンダリングも実装可能です。  
本記事では、サーバサイドに関するお話は省きます。  

SvelteKitプロジェクトの作成は、CLIツールを用いて質問に答えていけばOKです。

### 前提
私が作成した環境は以下になります。  

* OS: Ubuntu 22.04.2 LTS
* Node.js: v18.18.2
* npm v9.8.1

### 手順
ターミナルで以下のコマンドを実行します。
```
npm create svelte@latest svelte-chat
```

以降、プロジェクト作成に必要な質問が出てきますので答えていきます。  
私は以下のように回答しました。
```
┌  Welcome to SvelteKit!
│
◇  Which Svelte app template?
│  Skeleton project
│
◇  Add type checking with TypeScript?
│  Yes, using TypeScript syntax
│
◇  Select additional options (use arrow keys/space bar)
│  Add Prettier for code formatting
│
└  Your project is ready!
```

（質問回答後にターミナルに表示されますが）後は以下のコマンドを実行すれば、開発用サーバが立ち上がって実装が進められる状態になります。
```
Next steps:
  1: cd svelte-chat
  2: npm install
  3: git init && git add -A && git commit -m "Initial commit" (optional)     
  4: npm run dev -- --open
```

## 観察してみる
### ディレクトリ構成
作成されたプロジェクトのディレクトリ構造は以下のような感じです。  
基本的に`routes`ディレクトリの下に`.svelte`ファイルを作成してページを実装していくこととなります。  
```
.
├── ./README.md
├── ./node_modules
├── ./package-lock.json
├── ./package.json
├── ./src
│   ├── ./src/app.d.ts
│   ├── ./src/app.html
│   ├── ./src/lib
│   │   └── index.ts
│   └── ./src/routes
│       └── +page.svelte
├── ./static
│   └── favicon.png
├── ./svelte.config.js
├── ./tsconfig.json
└── ./vite.config.ts
```

`routes`内のディレクトリ構成は、ユーザがアクセスするURLのパスと対応します。  
すなわちディレクトリ構成によってルーティングを定義できます。例えば、以下のようになります。
* `http://<hostname>/`でアクセス　→　`routes/+page.svelte`の内容が表示される
* `http://<hostname>/hoge/fuga`でアクセス　→　`routes/hoge/fuga/+page.svelte`の内容が表示される

その他細かいルーティングの仕様は、[公式ページ](https://kit.svelte.jp/docs/routing)に記載がありますので確認してみてください。  

### Svelteファイル
Svelteファイルは以下の構成で記述していきます。  
1つのファイルの中にページ（コンポーネント）に関わるHTML、ロジック、スタイルを全て記述できます（vueファイルと同じですね）。
```
<script lang="ts">
    // 画面のロジックを記述
    // JavaScript, TypeScriptどちらも可。
　　　　let message = "Hello, World!!";
</script>

<div>
    メッセージ:
    <div id="message-element">
        { message } <!-- TypeScriptで定義した変数と紐づけ -->
    </div>
</div>

<style>
/* CSSを記述 */
#message-element {
    color:blue;
    font-size: x-large;
}
</style>
```

[公式サイト](https://svelte.dev/examples/hello-world)にサンプルが大量に転がっているので参考にしてみてください。

## SvelteプロジェクトにFirebaseをセットアップ
### 準備
FirebaseのWebサイトにて、以下作業を行います。
* Firebaseプロジェクトの作成
* 認証(Firebase Authentication)とデータベース(Firebase Realtime Database)の有効化
  * 認証はログイン／ログアウト機能実装のため。
  * データベースはチャットデータ管理のため。

こちらの作業は以下のページを参考にさせていただきました。  
https://zenn.dev/hisho/books/617d8f9d6bd78b/viewer/chapter3

### セットアップ
Firebaseを使うためのnpmモジュールを追加します。
```
npm install --save firebase
```

Svelteコード上でFirebaseを初期化します。  
```typescript:./src/lib/firebase.ts
import { getAuth, type Auth } from "firebase/auth";
import { getDatabase, ref, type Database, type DatabaseReference } from "firebase/database";
import { getApps, initializeApp, type FirebaseApp } from 'firebase/app';

export let app: FirebaseApp | null = null;
export let db: Database | null = null;
export let dbRef: DatabaseReference | null = null;
export let auth: Auth | null = null;

export async function initFirebaseApp() {
  if (getApps().length !== 0) {
    console.log('Firebase app already initialized.');
    return;
  }
  const firebaseConfig = {
    apiKey: import.meta.env.VITE_API_KEY,
    authDomain: import.meta.env.VITE_AUTH_DOMAIN,
    projectId: import.meta.env.VITE_PROJECT_ID,
    storageBucket: import.meta.env.VITE_STORAGE_BUCKET,
    messagingSenderId: import.meta.env.VITE_MESSAGING_SENDER_ID,
    appId: import.meta.env.VITE_APP_ID
  };
  app = initializeApp(firebaseConfig);
  auth = getAuth(app);
  db = getDatabase(app);
  dbRef = ref(db, 'chat');
}
```

```typescript:./src/routes/+layout.svelte
import { initFirebaseApp } from '$lib/firebase';

onMount(async () => {
  await initFirebaseApp();
});
```

firebaseConfigの値は、環境変数として`.env`ファイルに定義します。  
Svelteからは`import.meta.env`の形で取得可能です。  
```./.env
VITE_API_KEY=xxxx
VITE_AUTH_DOMAIN=xxxx
VITE_PROJECT_ID=xxxx
VITE_STORAGE_BUCKET=xxxx
VITE_MESSAGING_SENDER_ID=xxxx
VITE_APP_ID=xxxx
```

## チャットツールの実装
Svelte + Firebaseを使ってチャットツール(具体的には以下の機能)を実装しました。  
* ログイン／ログアウト機能
* チャット閲覧／投稿機能

できた画面がこちら。  
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341817/04f6593f-47ec-9379-f684-b49b744f1872.png" width="500">

ソースコードはGitHubに上げていますので良ければご覧下さい。  
https://github.com/imura-nalgo/svelte-chat

## 感想
### Svelteについて
Vue.js(さらに言えばNuxt.js)経験者ならば、何の違和感もなく使えると思います。  
Svelteファイルの構成はVueファイルと同じですし、ルーティングの定義方法はNuxt.jsとかなり似ていたためです。  

検索して出てくる情報は、Angular／React／Vueと比べると少ないです(これから増えてくるとは思いますが)。  
ただその分公式ドキュメントやexampleがかなり充実しており、かつ体系的に学習できるように作ってある印象でした。

### Firebaseについて
便利すぎる！！というのが第一印象です。極端に言えば、サーバ側の事を何も考慮しなくても簡単なWebサイトなら作成可能だと思いました。  
今回は「Firebase Authentication」と「Firebase Realtime Database」の2つを用いましたが、   
認証ではユーザ情報のセッション管理方法等は全く考慮不要でした。Firebaseが提供している方法を選択して、指定のメソッドを呼び出しただけです。
データベースについても、今回はチャットの投稿／削除時に画面を更新する必要があったため、レコード追加／削除イベントをキャッチする必要がありました。  
これもFirebaseが提供しているコールバック関数に実装するだけで実現できてしまいました。

### 最後に
Svelte、Firebaseともにまだまださわりの部分しか触れていないので、次はもう少し難しいものを作ってみようと思いました。