firebaseとhyperappで自分用のLGTMのようなものを作る

[シェアフル Advent Calendar 15日目](https://adventar.org/calendars/3523)の記事になります。
業務でサーバサイド Kotlin をやってるエンジニアがノリでフロントを書きましたという内容になっています。

## はじめに

普段はサーバサイド Kotlin を書いており、フロント側のコードを書く機会があまりないので、自分の勉強のためにテキトーな感じで [LGTM](https://lgtm.lol/)のようなものを作成しています。最初はサーバサイドを Kotlin+gRPC で作成していましたが、サーバ側でたいした処理を行わないので　Kotlin+gRPC の構成をやめて firebase に変更しました。まだ作成途中ですが、自分の [github](https://github.com/mizumura3/hyperapp-firebase-sample) にソースコードを置いてありますので、興味がありましたらご覧になってください。

firebase にデプロイしたサンプルアプリは[こちら](https://lgtm-ika-b1225.firebaseapp.com/)

※ローカルホストで起動して画像をアップロードするには以下の手順が必要です。

- firebase-tools のインストール
- firebase に自分のアカウントでログイン
- apikey を設定する
- Cloud Storage を有効にする
- Firestore を有効にする

## hyperapp を採用した理由

はやりの React か Vue.js で作るのがいいのかなとも思いましたが、数ヶ月前の開発合宿で触る機会のあった [hyperapp](https://github.com/jorgebucaran/hyperapp) は覚えることが非常に少なく簡単に画面を作れたので、これなら最近のフロントエンドに疎いエンジニアでも手軽に画面のコードを書けるぞと思ったので採用しました。

## 画面の説明

画面はこんな感じです。
![qiita_1.png](https://qiita-image-store.s3.amazonaws.com/0/210588/1bfe28db-b2a5-3308-d473-9dd522542bfd.png)

テキストボックスには LGTM を markdown 記法で書いてあり、「Copy」ボタンを押すと URL がクリップボードにコピーされます。コピーボタンの実装はググったら出てきたのをちょっと改良して組み込みました。`navigator.clipboard` を使えば簡単に実装できるんですね。

```
[![LGTM](https://firebasestorage.googleapis.com/v0/b/lgtm-ika-b1225.appspot.com/o/images%2Fkanashimi.png?alt=media&token=47d6cb70-2d14-4b6d-bd0c-cfa1a070e235)]
```

## 画像ファイルのアップロード処理について
Submit ボタンから画像ファイルをアップロードする画面に遷移し、画像ファイルは [Cloud Storage](https://firebase.google.com/docs/storage/?hl=ja) にアップロードしてます。
コードは以下になります。

```js

  // ファイルをアップロードする
  save: () => state => {
    const uploadTask = firebaseApp
      .storage()
      .ref()
      .child("/images/" + state.uploadFile.input.file.name)
      .put(state.uploadFile.input.file);
    uploadTask.on("state_changed", {
      complete: () => {
        uploadTask.snapshot.ref.getDownloadURL().then(downloadUrl => {
          console.log(downloadUrl);
          firestoreApp.collection("images").add({ url: downloadUrl });
        });
      }
    });
  }
```

Cloud Storage にアップロードしたファイルの url を取得し、アップロードが成功したら Firestore に url を追加する といった処理を行っています。これだけの処理で画像ファイルのアップロードと url の保持ができるので、Kotlin+gRPC サーバなんていらんかったんや・・という気持ちになりました。Cloud Functions で Cloud Storage にアップロードされたイベントを拾って Firestore にアップロードされたファイルの url を追加する というやり方でもできるそうですが、Cloud Functions を追加するのが面倒だったのと、Cloud Storage にアップロードした際の戻り値でファイルの url が取得できたので、このままにしておこうと思います。

## 画像ファイルの取得について

```js
  // ファイルを取得する
  items: () => async (state, actions) => {
    const result = new Array();
    await firestoreApp
      .collection("images")
      .get()
      .then(querySnapshot => {
        querySnapshot.forEach(doc => {
          console.log(doc.id, " => ", doc.data().url);
          result.push(doc.data().url);
        });
      });
    actions.setItems(result);
  }
```

なんとなくこんな感じで書けば動くだろう 的なノリで書いたら動きましたというコードです。
parcel のバージョンを最近あげたら `async/await` が動かなくなり、解決するのに2時間くらいかかりました・・。
ちょっと前まで使用していた `babel-preset-hyperapp` が babel 7.x系に対応していなかったので削除し、`@babel/plugin-transform-runtime` を追加しました。また`.babelrc` に以下を追記したら解決しました。

```json

  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "regenerator": true
      }
    ]
  ]
```

babel あんまりよくわかっていないので google で調べてこんな感じのノリで書けばいけるだろうという感じでやったら動きました。正しい解決方法はフロントエンド詳しい人に聞こうと思います！

## 追加したい機能と修正したいところ
- 画像をアップロードしてから HOME を押してもアップロードしたファイルが反映されないのでリロードする必要あり
- ページングがない
- ファイルサイズの制限を判定していないので、でかいファイルをアップロードされると無料プランが吹っ飛ぶ

週末にちょっとずつ改良したいと思います。

## まとめ

普段はバックエンドのコードを書いているので、javascript を書いたり画面を作るのは新鮮で楽しいです。
ちょっとした個人開発のアプリくらいなら hyperapp で十分じゃないかという気持ちです。
hyperapp はライブラリが少ないので、凝ったことをやろうとするなら React か Vue もしくは Angular になるのかなーという感じがありました。
今年は冬休みが長いので、[Switch 版の Diablo3](https://us.diablo3.com/ja/switch) をやりながら冬休み中に完成させる気持ちで頑張ろうと思います。